🎈 perf: 更新多个主页
This commit is contained in:
parent
20ca3cdf0a
commit
800e7cb08e
@ -15,5 +15,6 @@ module.exports = {
|
||||
rules: {
|
||||
'vue/no-unused-vars': 'warn',
|
||||
'vue/multi-word-component-names': 'off',
|
||||
'vue/valid-v-for': 'off',
|
||||
},
|
||||
};
|
||||
|
30
src/App.vue
30
src/App.vue
@ -36,15 +36,13 @@
|
||||
@click="showVerticalHeaderMenu = true"
|
||||
/>
|
||||
</el-header>
|
||||
<el-main class="app-main" v-loading="!!pageStore.pageLoadingCount">
|
||||
<el-container>
|
||||
<router-view v-slot="{ Component: comp }">
|
||||
<transition appear mode="out-in" name="app-router-view">
|
||||
<component class="app-router-component" :is="comp" />
|
||||
</transition>
|
||||
</router-view>
|
||||
</el-container>
|
||||
</el-main>
|
||||
<el-container class="app-sub-page-wrapper" v-loading="!!pageStore.pageLoadingCount">
|
||||
<router-view v-slot="{ Component: comp }">
|
||||
<transition appear mode="out-in" name="app-sub-page">
|
||||
<component class="app-sub-page" :is="comp" />
|
||||
</transition>
|
||||
</router-view>
|
||||
</el-container>
|
||||
</el-container>
|
||||
<login-register-dialog
|
||||
v-model="showLoginRegisterDialog"
|
||||
@ -104,23 +102,21 @@
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
.app-main {
|
||||
--el-main-padding: 0 0 0 0;
|
||||
.app-sub-page-wrapper {
|
||||
margin-top: var(--header-height);
|
||||
display: flex;
|
||||
}
|
||||
.app-router-component {
|
||||
.app-sub-page {
|
||||
&:not(.keep-padding) {
|
||||
--el-main-padding: 0;
|
||||
}
|
||||
}
|
||||
.app-router-view-enter-active,
|
||||
.app-router-view-leave-active {
|
||||
.app-sub-page-enter-active,
|
||||
.app-sub-page-leave-active {
|
||||
transition: opacity 0.25s;
|
||||
}
|
||||
|
||||
.app-router-view-enter-from,
|
||||
.app-router-view-leave-to {
|
||||
.app-sub-page-enter-from,
|
||||
.app-sub-page-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
@ -8,15 +8,15 @@
|
||||
<template v-if="resp.data.total">
|
||||
<div><slot :data="resp.data.data"></slot></div>
|
||||
<el-pagination
|
||||
v-if="!hidePager"
|
||||
class="m-t-10px"
|
||||
:class="paginationClass"
|
||||
:background="paginationBackground"
|
||||
:layout="paginationLayout"
|
||||
:total="total"
|
||||
:page-size="num"
|
||||
1
|
||||
:current="page"
|
||||
hide-on-single-page
|
||||
@change="refresh"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
@ -30,18 +30,24 @@
|
||||
<script generic="T extends AnyPagedRespSchema" setup lang="ts">
|
||||
import { request } from '@/api';
|
||||
import type { AnyPagedRespSchema, SucceedRespOf } from '@/schemas/response';
|
||||
import { computed } from '@vue/reactivity';
|
||||
import { computed } from 'vue';
|
||||
import type { AxiosRequestConfig } from 'axios';
|
||||
import { ref } from 'vue';
|
||||
import type { z } from 'zod';
|
||||
type Succeed = SucceedRespOf<z.infer<T>>;
|
||||
type DataList = Succeed['data']['data'];
|
||||
const { schema, getRequestOptions, num } = defineProps<{
|
||||
const {
|
||||
schema,
|
||||
getRequestOptions,
|
||||
num,
|
||||
hidePager = false,
|
||||
} = defineProps<{
|
||||
schema: T;
|
||||
num: number;
|
||||
paginationClass?: string;
|
||||
paginationBackground?: boolean;
|
||||
paginationLayout?: string;
|
||||
hidePager?: boolean;
|
||||
getRequestOptions: (page: number, num: number) => AxiosRequestConfig;
|
||||
}>();
|
||||
defineSlots<{
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-menu-item class="festival-menu-item flex" :index="to.fullPath">
|
||||
<el-menu-item class="festival-menu-item flex" :index="to">
|
||||
<img :src="imgURL" class="festival-menu-item__img" />
|
||||
<div class="festival-menu-item__title">{{ title }}</div>
|
||||
</el-menu-item>
|
||||
|
@ -7,20 +7,34 @@
|
||||
@select="$emit('select')"
|
||||
>
|
||||
<el-menu-item index="/">首页</el-menu-item>
|
||||
<el-menu-item v-if="userStore.hasRoutePermission('/club')" index="/club">社团</el-menu-item>
|
||||
<el-sub-menu class="festival-menu" index="festival">
|
||||
<template #title>社团文化节</template>
|
||||
<template v-for="{ imgURL, title, to } of festivalMenuItems">
|
||||
<festival-menu-item
|
||||
v-if="userStore.hasRoutePermission(to)"
|
||||
:key="to.fullPath"
|
||||
:imgURL="imgURL"
|
||||
:title="title"
|
||||
:to="to"
|
||||
/>
|
||||
</template>
|
||||
<el-sub-menu index="club">
|
||||
<template #title>社团</template>
|
||||
<el-menu-item
|
||||
v-if="userStore.isRouteAccessible('/club') === true"
|
||||
index="/club"
|
||||
class="text-center"
|
||||
>
|
||||
社团列表
|
||||
</el-menu-item>
|
||||
<el-sub-menu
|
||||
v-if="festivalMenuItems.some(({ to }) => userStore.isRouteAccessible(to) === true)"
|
||||
class="festival-menu"
|
||||
index="festival"
|
||||
>
|
||||
<template #title>社团文化节</template>
|
||||
<template v-for="{ imgURL, title, to } of festivalMenuItems">
|
||||
<festival-menu-item
|
||||
v-if="userStore.isRouteAccessible(to) === true"
|
||||
:imgURL="imgURL"
|
||||
:title="title"
|
||||
:to="to"
|
||||
/>
|
||||
</template>
|
||||
</el-sub-menu>
|
||||
</el-sub-menu>
|
||||
<el-menu-item v-if="userStore.hasRoutePermission('/manage')" index="/manage">管理</el-menu-item>
|
||||
<el-menu-item v-if="userStore.isRouteAccessible('/manage') === true" index="/manage">
|
||||
管理
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
@ -32,36 +46,39 @@
|
||||
flex: 1;
|
||||
margin-right: 10px;
|
||||
}
|
||||
&.el-menu--vertical {
|
||||
--el-menu-item-height: 40px;
|
||||
--el-menu-sub-item-height: var(--el-menu-item-height);
|
||||
}
|
||||
:is(.el-menu-item, .el-sub-menu__title) {
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script lang="ts">
|
||||
export interface MenuItem {
|
||||
export type MenuItem = {
|
||||
imgURL: string;
|
||||
title: string;
|
||||
to: RouteLocationGeneric;
|
||||
}
|
||||
to: string;
|
||||
};
|
||||
export type MenuItemNoImage = StrictOmit<MenuItem, 'imgURL'>;
|
||||
</script>
|
||||
<script setup lang="ts">
|
||||
import FestivalMenuItem from '@/components/app/FestivalMenuItem.vue';
|
||||
import router from '@/router';
|
||||
import { useUserStore } from '@/stores/user';
|
||||
import type { StrictOmit } from '@/utils/types';
|
||||
import { reactive } from 'vue';
|
||||
import type { RouteLocationGeneric } from 'vue-router';
|
||||
const userStore = useUserStore();
|
||||
|
||||
const festivalMenuItems = reactive<MenuItem[]>([
|
||||
{
|
||||
imgURL: '/2048.png',
|
||||
title: '2048',
|
||||
to: router.resolve('/2048'),
|
||||
to: '/2048',
|
||||
},
|
||||
{
|
||||
imgURL: '/gobang.svg',
|
||||
title: '五子棋',
|
||||
to: router.resolve('/gobang'),
|
||||
to: '/gobang',
|
||||
},
|
||||
]);
|
||||
defineProps<{
|
||||
|
68
src/components/club/ClubList.vue
Normal file
68
src/components/club/ClubList.vue
Normal file
@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<paged-wrapper
|
||||
pagination-background
|
||||
pagination-class="w-min"
|
||||
:hide-pager="brief"
|
||||
:schema="clubListRespSchema"
|
||||
:num="10"
|
||||
:get-request-options="getClubListRequestOptions"
|
||||
>
|
||||
<template #default="{ data }">
|
||||
<div class="flex gap-10px">
|
||||
<div
|
||||
v-for="club of data"
|
||||
class="flex flex-col items-center"
|
||||
:class="itemClass"
|
||||
:key="club.id"
|
||||
@click="onItemClick(club)"
|
||||
>
|
||||
<el-avatar
|
||||
shape="square"
|
||||
class="select-none"
|
||||
:size="100"
|
||||
:src="getAvatarURL(club.avatar)"
|
||||
>
|
||||
<div class="text-black">暂无图片</div>
|
||||
</el-avatar>
|
||||
<div class="text-5 font-bold">{{ club.name }}</div>
|
||||
<el-text
|
||||
v-if="userStore.userInfo?.club?.id === club.id"
|
||||
type="success"
|
||||
class="flex items-center"
|
||||
>
|
||||
<icon-ep-success-filled />
|
||||
<b>已加入</b>
|
||||
</el-text>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #empty>
|
||||
<div class="font-bold text-5">暂无社团</div>
|
||||
</template>
|
||||
</paged-wrapper>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { getAvatarURL } from '@/api';
|
||||
import PagedWrapper from '@/components/PagedWrapper.vue';
|
||||
import { clubListRespSchema, type Club } from '@/schemas/response';
|
||||
import { useUserStore } from '@/stores/user';
|
||||
import type { ComponentProps } from 'vue-component-type-helpers';
|
||||
const userStore = useUserStore();
|
||||
const { brief = false } = defineProps<{
|
||||
brief?: boolean;
|
||||
itemClass?: string;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
click: [club: Club];
|
||||
}>();
|
||||
function onItemClick(club: Club) {
|
||||
emit('click', club);
|
||||
}
|
||||
const getClubListRequestOptions: ComponentProps<typeof PagedWrapper>['getRequestOptions'] = (
|
||||
page,
|
||||
num,
|
||||
) => ({
|
||||
url: '/api/club/',
|
||||
params: { page, num },
|
||||
});
|
||||
</script>
|
27
src/components/main/MainCard.vue
Normal file
27
src/components/main/MainCard.vue
Normal file
@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<el-card class="main-page-card">
|
||||
<template #header>
|
||||
<div class="flex justify-between">
|
||||
<b>{{ title }}</b>
|
||||
<router-link custom v-slot="{ navigate }" :to="to">
|
||||
<div class="cursor-pointer flex items-center" @click="navigate">
|
||||
<b>查看更多</b>
|
||||
<icon-ep-d-arrow-right />
|
||||
</div>
|
||||
</router-link>
|
||||
</div>
|
||||
</template>
|
||||
<slot></slot>
|
||||
</el-card>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
.main-page-card {
|
||||
--el-card-padding: 15px;
|
||||
}
|
||||
</style>
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
title: string;
|
||||
to: string;
|
||||
}>();
|
||||
</script>
|
11
src/components/manage/ManageBreadcrumb.vue
Normal file
11
src/components/manage/ManageBreadcrumb.vue
Normal file
@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<el-card>
|
||||
<el-breadcrumb>
|
||||
<template v-for="matched in $route.matched">
|
||||
<el-breadcrumb-item v-if="matched.meta.breadcrumb" :to="$router.resolve(matched)">
|
||||
{{ matched.meta.breadcrumb }}
|
||||
</el-breadcrumb-item>
|
||||
</template>
|
||||
</el-breadcrumb>
|
||||
</el-card>
|
||||
</template>
|
@ -10,6 +10,7 @@ declare module 'vue-router' {
|
||||
shouldLogin?: boolean;
|
||||
userPermissionId?: MaybeArray<UserPermissionId>;
|
||||
clubPermissionId?: MaybeArray<ClubPermissionId>;
|
||||
breadcrumb?: string;
|
||||
}
|
||||
}
|
||||
const routes: RouteRecordRaw[] = [
|
||||
@ -17,6 +18,9 @@ const routes: RouteRecordRaw[] = [
|
||||
path: '/',
|
||||
name: 'Home',
|
||||
component: () => import('@/views/MainPage.vue'),
|
||||
meta: {
|
||||
breadcrumb: '首页',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/user/:id',
|
||||
@ -43,8 +47,18 @@ const routes: RouteRecordRaw[] = [
|
||||
},
|
||||
{
|
||||
path: '/club',
|
||||
name: 'Club',
|
||||
component: () => import('@/views/ClubPage.vue'),
|
||||
name: 'ClubList',
|
||||
component: () => import('@/views/club/ClubListPage.vue'),
|
||||
},
|
||||
{
|
||||
path: '/club/press',
|
||||
name: 'ClubPressList',
|
||||
component: () => import('@/views/club/ClubPressListPage.vue'),
|
||||
},
|
||||
{
|
||||
path: '/club/:id',
|
||||
name: 'ClubInfo',
|
||||
component: () => import('@/views/club/ClubInfoPage.vue'),
|
||||
meta: {
|
||||
clubPermissionId: ClubPermissionId.CLUB_PAGE,
|
||||
},
|
||||
@ -82,7 +96,25 @@ const routes: RouteRecordRaw[] = [
|
||||
path: '/manage',
|
||||
name: 'Manage',
|
||||
component: () => import('@/views/manage/ManagePage.vue'),
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'ManageMain',
|
||||
component: () => import('@/views/manage/ManageMainPage.vue'),
|
||||
},
|
||||
{
|
||||
path: 'user',
|
||||
name: 'ManageUser',
|
||||
component: () => import('@/views/manage/ManageUserPage.vue'),
|
||||
meta: {
|
||||
userPermissionId: UserPermissionId.MANAGE_USER,
|
||||
breadcrumb: '用户管理',
|
||||
},
|
||||
},
|
||||
],
|
||||
redirect: { name: 'ManageMain' },
|
||||
meta: {
|
||||
breadcrumb: '系统首页',
|
||||
userPermissionId: UserPermissionId.MANAGE_PAGE,
|
||||
},
|
||||
},
|
||||
@ -103,12 +135,11 @@ const router = createRouter({
|
||||
router.beforeEach(async (to) => {
|
||||
const userStore = useUserStore();
|
||||
const pageStore = usePageStore();
|
||||
const { userPermissionId, clubPermissionId } = to.meta;
|
||||
pageStore.setNewRouteId(to);
|
||||
if (!userStore.userInfo) {
|
||||
const succeed = await userStore.updateSelfUserInfo();
|
||||
if (!succeed) {
|
||||
if (userPermissionId === undefined) {
|
||||
if (to.meta.userPermissionId === undefined) {
|
||||
return true;
|
||||
}
|
||||
return pageStore.createTempErrorRoute(
|
||||
@ -119,14 +150,10 @@ router.beforeEach(async (to) => {
|
||||
);
|
||||
}
|
||||
}
|
||||
if (to.meta.shouldLogin && !userStore.logined) {
|
||||
return pageStore.createTempErrorRoute({ type: PageErrorType.NOT_LOGIN }, to);
|
||||
const type = userStore.isRouteAccessible(to.fullPath);
|
||||
if (type !== true) {
|
||||
return pageStore.createTempErrorRoute({ type }, to);
|
||||
}
|
||||
if (
|
||||
!userStore.hasUserPermission(userPermissionId) ||
|
||||
!userStore.hasClubPermission(clubPermissionId)
|
||||
)
|
||||
return pageStore.createTempErrorRoute({ type: PageErrorType.NO_PERMISSION }, to);
|
||||
return true;
|
||||
});
|
||||
router.afterEach((to) => {
|
||||
|
@ -1,4 +1,3 @@
|
||||
type WithExtra<T extends Record<string, unknown>> = T & Neusoft.ExtraFields;
|
||||
export namespace Neusoft {
|
||||
export type BaseResp<T extends Record<string, unknown>> = {
|
||||
code: number;
|
||||
@ -21,13 +20,13 @@ export namespace Neusoft {
|
||||
data: T;
|
||||
}>;
|
||||
}
|
||||
export namespace Neusoft {
|
||||
export type Gallery = WithExtra<{
|
||||
export namespace Neusoft.SmartParty {
|
||||
export type Gallery = ExtraFields & {
|
||||
id: number;
|
||||
hotNewsId: number;
|
||||
imageUrl: string;
|
||||
imageName: string;
|
||||
}>;
|
||||
};
|
||||
export type GalleryResp = PagedResp<Gallery>;
|
||||
}
|
||||
export namespace Neusoft {
|
||||
|
@ -3,7 +3,7 @@ import { defineStore } from 'pinia';
|
||||
import { computed } from 'vue';
|
||||
|
||||
export const useMediaStore = defineStore('media', () => {
|
||||
const smLess = useMediaQuery('(width < 640px)');
|
||||
const smLess = useMediaQuery('(width < 576px)');
|
||||
const smOnly = computed(() => !smLess.value && mdLess.value);
|
||||
const mdLess = useMediaQuery('(width < 768px)');
|
||||
const mdOnly = computed(() => !mdLess.value && lgLess.value);
|
||||
|
@ -9,8 +9,9 @@ import { ElMessage } from 'element-plus';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { defineStore } from 'pinia';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import type { RouteLocationGeneric, RouteMeta } from 'vue-router';
|
||||
import type { RouteLocationAsRelativeGeneric, RouteMeta } from 'vue-router';
|
||||
import { z } from 'zod';
|
||||
import { PageErrorType } from '../views/ErrorPage.vue';
|
||||
|
||||
type Permission = z.infer<typeof idAndNameSchema>;
|
||||
export const useUserStore = defineStore('user', () => {
|
||||
@ -39,18 +40,17 @@ export const useUserStore = defineStore('user', () => {
|
||||
);
|
||||
const hasClubPermission = (clubPermissionId: RouteMeta['clubPermissionId']) =>
|
||||
!clubPermissionId || ensureArray(clubPermissionId).every((id) => clubPermissions.value.has(id));
|
||||
function hasRoutePermission(to: string | RouteLocationGeneric) {
|
||||
if (typeof to === 'string') {
|
||||
to = router.resolve(to);
|
||||
}
|
||||
function isRouteAccessible(to: string | RouteLocationAsRelativeGeneric): true | PageErrorType {
|
||||
const {
|
||||
meta: { shouldLogin, userPermissionId, clubPermissionId },
|
||||
} = to;
|
||||
return (
|
||||
(!shouldLogin || logined.value) &&
|
||||
hasUserPermission(userPermissionId) &&
|
||||
hasClubPermission(clubPermissionId)
|
||||
);
|
||||
} = router.resolve(to);
|
||||
if (shouldLogin && !logined.value) {
|
||||
return PageErrorType.NOT_LOGIN;
|
||||
}
|
||||
if (!hasUserPermission(userPermissionId) || !hasClubPermission(clubPermissionId)) {
|
||||
return PageErrorType.NO_PERMISSION;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
const initializing = ref(false);
|
||||
const logined = computed(() => (userInfo.value && userInfo.value.id !== -1) ?? false);
|
||||
@ -101,7 +101,7 @@ export const useUserStore = defineStore('user', () => {
|
||||
hasUserPermission,
|
||||
clubPermissions,
|
||||
hasClubPermission,
|
||||
hasRoutePermission,
|
||||
isRouteAccessible,
|
||||
initializing,
|
||||
isSelf,
|
||||
updateSelfUserInfo,
|
||||
|
@ -8,3 +8,4 @@ export type Override<T extends object, R extends Partial<T> = {}, O extends keyo
|
||||
> &
|
||||
R;
|
||||
export type Nullable<T = never> = T | null | undefined;
|
||||
export type StrictOmit<T, K extends keyof T> = Omit<T, K>;
|
||||
|
@ -1,5 +0,0 @@
|
||||
<template>
|
||||
<el-main></el-main>
|
||||
</template>
|
||||
<style lang="scss" scoped></style>
|
||||
<script lang="ts" setup></script>
|
@ -2,57 +2,25 @@
|
||||
<el-main
|
||||
v-loading="loading"
|
||||
element-loading-text="正在加载"
|
||||
class="page-main keep-padding flex justify-center bg-white"
|
||||
class="keep-padding flex justify-center bg-white"
|
||||
>
|
||||
<div class="max-sm-w-350px sm-w-550px md-w-700px lg-w-950px xl-w-1200px flex flex-col gap-10px">
|
||||
<div class="w-1200px flex flex-col gap-10px">
|
||||
<el-carousel class="rounded-10px" height="300px">
|
||||
<el-carousel-item v-for="g of gallery" class="slider-item">
|
||||
<el-carousel-item v-for="g of gallery" class="slider-item" :key="g.id">
|
||||
<img class="slider-item__img" :src="Neusoft.SMART_PARTY_BASE_URL + g.imageUrl" />
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
<div class="grid grid-cols-2 gap-10px">
|
||||
<div>
|
||||
<div class="title">社团资讯</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="title">社团活动</div>
|
||||
</div>
|
||||
<div class="grid-col-span-2">
|
||||
<div class="title m-b-10px">社团列表</div>
|
||||
<paged-wrapper
|
||||
pagination-background
|
||||
:schema="clubListRespSchema"
|
||||
:num="10"
|
||||
pagination-class="w-min"
|
||||
pagination-layout="prev, pager, next"
|
||||
:get-request-options="getClubListRequestOptions"
|
||||
>
|
||||
<template #default="{ data }">
|
||||
<div
|
||||
class="grid gap-10px justify-center max-sm-grid-cols-3 sm-grid-cols-5 md-grid-cols-7 lg-grid-cols-9"
|
||||
>
|
||||
<div v-for="club of data" class="flex flex-col items-center">
|
||||
<el-avatar :size="90" :src="getAvatarURL(club.avatar)">
|
||||
<div class="text-black">暂无图片</div>
|
||||
</el-avatar>
|
||||
<div class="text-5 font-bold">{{ club.name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #empty>
|
||||
<div class="font-bold text-5">暂无社团</div>
|
||||
</template>
|
||||
</paged-wrapper>
|
||||
</div>
|
||||
<main-card class="max-lg-col-span-2" title="社团资讯" to="/club/press"></main-card>
|
||||
<main-card class="max-lg-col-span-2" title="社团活动" to="/club"></main-card>
|
||||
<main-card class="col-span-2" title="社团列表" to="/club">
|
||||
<club-list brief />
|
||||
</main-card>
|
||||
</div>
|
||||
</div>
|
||||
</el-main>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.page-main {
|
||||
// background-image: url('/club-background.svg');
|
||||
// background-size: 100px;
|
||||
}
|
||||
.slider-item__img {
|
||||
@apply w-full h-full select-none rounded-10px;
|
||||
}
|
||||
@ -61,24 +29,16 @@
|
||||
}
|
||||
</style>
|
||||
<script lang="ts" setup>
|
||||
import { getAvatarURL, requestRaw } from '@/api';
|
||||
import PagedWrapper from '@/components/PagedWrapper.vue';
|
||||
import { requestRaw } from '@/api';
|
||||
import ClubList from '@/components/club/ClubList.vue';
|
||||
import MainCard from '@/components/main/MainCard.vue';
|
||||
import { Neusoft } from '@/schemas/neusoft';
|
||||
import { clubListRespSchema } from '@/schemas/response';
|
||||
import { useUserStore } from '@/stores/user.js';
|
||||
import { ref } from 'vue';
|
||||
import type { ComponentProps } from 'vue-component-type-helpers';
|
||||
const userStore = useUserStore();
|
||||
const loading = ref(false);
|
||||
const gallery = ref<Neusoft.Gallery[]>();
|
||||
const getClubListRequestOptions: ComponentProps<typeof PagedWrapper>['getRequestOptions'] = (
|
||||
page,
|
||||
num,
|
||||
) => ({
|
||||
url: '/api/club/',
|
||||
params: { page, num },
|
||||
});
|
||||
requestRaw<Neusoft.GalleryResp>({
|
||||
const gallery = ref<Neusoft.SmartParty.Gallery[]>();
|
||||
requestRaw<Neusoft.SmartParty.GalleryResp>({
|
||||
url: `${Neusoft.SMART_PARTY_BASE_URL}/system/rotation/list`,
|
||||
addAuth: false,
|
||||
}).then((resp) => {
|
||||
|
1
src/views/club/ClubInfoPage.vue
Normal file
1
src/views/club/ClubInfoPage.vue
Normal file
@ -0,0 +1 @@
|
||||
<template>s</template>
|
32
src/views/club/ClubListPage.vue
Normal file
32
src/views/club/ClubListPage.vue
Normal file
@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<el-main class="keep-padding bg-white flex justify-center">
|
||||
<div class="w-1200px flex flex-col">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<b>社团列表</b>
|
||||
</template>
|
||||
<club-list item-class="cursor-pointer" @click="onClubClick" />
|
||||
</el-card>
|
||||
</div>
|
||||
<el-dialog v-model="showClubDialog">
|
||||
<template #title>社团信息</template>
|
||||
<template v-if="showClubDialog"></template>
|
||||
</el-dialog>
|
||||
</el-main>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.el-card {
|
||||
--el-card-padding: 15px;
|
||||
}
|
||||
</style>
|
||||
<script lang="ts" setup>
|
||||
import ClubList from '@/components/club/ClubList.vue';
|
||||
import type { Club } from '@/schemas/response';
|
||||
import { ref } from 'vue';
|
||||
const showingClub = ref<Club>();
|
||||
const showClubDialog = ref(false);
|
||||
function onClubClick(club: Club) {
|
||||
showingClub.value = club;
|
||||
showClubDialog.value = true;
|
||||
}
|
||||
</script>
|
3
src/views/club/ClubPressListPage.vue
Normal file
3
src/views/club/ClubPressListPage.vue
Normal file
@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<el-main></el-main>
|
||||
</template>
|
15
src/views/manage/ManageMainPage.vue
Normal file
15
src/views/manage/ManageMainPage.vue
Normal file
@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<el-main class="flex flex-col gap-10px">
|
||||
<manage-breadcrumb />
|
||||
<el-card body-class="flex">
|
||||
你好,
|
||||
<b>{{ userStore.userInfo!.name }}</b>
|
||||
!欢迎来到后台管理系统
|
||||
</el-card>
|
||||
</el-main>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import ManageBreadcrumb from '@/components/manage/ManageBreadcrumb.vue';
|
||||
import { useUserStore } from '@/stores/user';
|
||||
const userStore = useUserStore();
|
||||
</script>
|
@ -1,13 +1,66 @@
|
||||
<template>
|
||||
<el-main>
|
||||
<el-main class="flex">
|
||||
<el-container>
|
||||
<el-aside class="manage-page-aside">
|
||||
<el-menu>
|
||||
<el-menu-item></el-menu-item>
|
||||
<el-aside class="bg-white manage-page-aside">
|
||||
<el-menu router :default-active="$route.fullPath">
|
||||
<el-menu-item index="/manage">
|
||||
<el-icon><icon-ep-home-filled /></el-icon>
|
||||
系统首页
|
||||
</el-menu-item>
|
||||
<el-sub-menu index="1" v-if="userStore.hasUserPermission(UserPermissionId.MANAGE_USER)">
|
||||
<template #title>
|
||||
<el-icon><icon-ep-user-filled /></el-icon>
|
||||
<span>用户管理</span>
|
||||
</template>
|
||||
<el-menu-item v-if="userStore.isRouteAccessible('/manage/user')" index="/manage/user">
|
||||
<el-icon><icon-ep-user-filled /></el-icon>
|
||||
用户管理
|
||||
</el-menu-item>
|
||||
</el-sub-menu>
|
||||
</el-menu>
|
||||
</el-aside>
|
||||
<el-main></el-main>
|
||||
<el-container>
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition name="manage-sub-page" mode="out-in" appear>
|
||||
<component :is="Component" class="manage-sub-page" />
|
||||
</transition>
|
||||
</router-view>
|
||||
</el-container>
|
||||
</el-container>
|
||||
</el-main>
|
||||
</template>
|
||||
<style lang="scss" scoped></style>
|
||||
<style lang="scss" scoped>
|
||||
.manage-page-aside {
|
||||
--el-aside-width: 200px;
|
||||
.el-menu--vertical {
|
||||
--el-menu-item-height: 50px;
|
||||
border-right: none;
|
||||
}
|
||||
}
|
||||
.manage-sub-page {
|
||||
--el-main-padding: 10px;
|
||||
&-enter-active,
|
||||
&-leave-active {
|
||||
transition: opacity 0.25s;
|
||||
}
|
||||
&-enter-from,
|
||||
&-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script setup lang="ts">
|
||||
import { UserPermissionId } from '@/router/permissions';
|
||||
import { useMediaStore } from '@/stores/media';
|
||||
import { useUserStore } from '@/stores/user';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { onMounted } from 'vue';
|
||||
const { mdLess } = storeToRefs(useMediaStore());
|
||||
const userStore = useUserStore();
|
||||
onMounted(() => {
|
||||
if (mdLess.value) {
|
||||
ElMessage.warning('暂不支持移动端');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
9
src/views/manage/ManageUserPage.vue
Normal file
9
src/views/manage/ManageUserPage.vue
Normal file
@ -0,0 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import ManageBreadcrumb from '@/components/manage/ManageBreadcrumb.vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-main>
|
||||
<manage-breadcrumb />
|
||||
</el-main>
|
||||
</template>
|
@ -64,7 +64,7 @@ import { usernameLength } from '@/components/app/LoginRegisterDialog.vue';
|
||||
import { ordinarySchema, uploadAvatarRespSchema } from '@/schemas/response';
|
||||
import { useMediaStore } from '@/stores/media';
|
||||
import { useUserStore } from '@/stores/user';
|
||||
import { Close, Upload } from '@element-plus/icons-vue';
|
||||
import { Upload } from '@element-plus/icons-vue';
|
||||
import {
|
||||
ElMessage,
|
||||
type FormInstance,
|
||||
@ -74,7 +74,6 @@ import {
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { defineComponent, reactive, ref } from 'vue';
|
||||
import type { RouteLocationNormalized } from 'vue-router';
|
||||
const { smLess } = storeToRefs(useMediaStore());
|
||||
function validate({ params: { id } }: RouteLocationNormalized) {
|
||||
if (!useUserStore().isSelf(id as string)) {
|
||||
ElMessage.error('不能编辑其他用户哦');
|
||||
@ -89,6 +88,7 @@ export default defineComponent({
|
||||
<script setup lang="ts">
|
||||
const userStore = useUserStore();
|
||||
const uploading = ref(false);
|
||||
const { smLess } = storeToRefs(useMediaStore());
|
||||
const upload: UploadRequestHandler = async ({ file }) => {
|
||||
if (!file.type.startsWith('image')) {
|
||||
ElMessage.error('只能上传图片');
|
||||
|
@ -8,7 +8,7 @@
|
||||
<template v-if="!smLess">
|
||||
<div
|
||||
v-if="!loading"
|
||||
class="max-md-w-600px md-w-700px lg-w-950px flex flex-col"
|
||||
class="p-20px w-950px flex flex-col"
|
||||
:class="[{ 'gap-10px': userInfo }, !userInfo ? 'justify-center items-center' : '']"
|
||||
>
|
||||
<template v-if="userInfo">
|
||||
@ -75,7 +75,6 @@
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.page-main {
|
||||
--el-main-padding: 30px 0;
|
||||
background-image: url('/bg5.jpg');
|
||||
background-size: cover;
|
||||
}
|
||||
|
@ -12,5 +12,13 @@ export default defineConfig({
|
||||
},
|
||||
],
|
||||
],
|
||||
theme: {
|
||||
breakpoints: {
|
||||
sm: '576px',
|
||||
md: '768px',
|
||||
lg: '1024px',
|
||||
xl: '1280px',
|
||||
},
|
||||
},
|
||||
transformers: [transformerDirectives()],
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user