添加管理端角色和基础框架
This commit is contained in:
parent
ac51f828d7
commit
4de9ed28df
@ -56,7 +56,7 @@ public class SecurityConfiguration {
|
||||
.requestMatchers("/api/auth/**", "/error").permitAll()
|
||||
.requestMatchers("/images/**").permitAll()
|
||||
.requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll()
|
||||
.anyRequest().hasAnyRole(Const.ROLE_DEFAULT)
|
||||
.anyRequest().hasAnyRole(Const.ROLE_DEFAULT, Const.ROLE_ADMIN)
|
||||
)
|
||||
.formLogin(conf -> conf
|
||||
.loginProcessingUrl("/api/auth/login")
|
||||
|
@ -22,6 +22,7 @@ public final class Const {
|
||||
public final static String MQ_MAIL = "mail";
|
||||
//用户角色
|
||||
public final static String ROLE_DEFAULT = "user";
|
||||
public final static String ROLE_ADMIN = "admin";
|
||||
//论坛相关
|
||||
public final static String FORUM_WEATHER_CACHE = "weather:cache:";
|
||||
public final static String FORUM_IMAGE_COUNTER = "forum:image:";
|
||||
|
@ -7,7 +7,7 @@ spring:
|
||||
mail:
|
||||
host: smtp.163.com
|
||||
username: javastudy111@163.com
|
||||
password: QGLYGBVECIASGPWQ
|
||||
password: SFxfE39P9Wf2icKi
|
||||
rabbitmq:
|
||||
addresses: localhost
|
||||
username: admin
|
||||
@ -44,4 +44,4 @@ spring:
|
||||
username: 'minio'
|
||||
password: 'password'
|
||||
weather:
|
||||
key: 7abbc24d3b4443b597a3b3c676e0f221
|
||||
key: 115bcaf74fb24f73844365290d85862b
|
||||
|
@ -11,13 +11,16 @@ useDark({
|
||||
useDark({
|
||||
onChanged(dark) { useToggle(dark) }
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header>
|
||||
<div class="wrapper">
|
||||
<router-view/>
|
||||
<router-view v-slot="{ Component }">
|
||||
<keep-alive>
|
||||
<component :is="Component"/>
|
||||
</keep-alive>
|
||||
</router-view>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
88
my-project-frontend/src/components/UserInfo.vue
Normal file
88
my-project-frontend/src/components/UserInfo.vue
Normal file
@ -0,0 +1,88 @@
|
||||
<script setup>
|
||||
import router from "@/router";
|
||||
import {Back, Message, Operation, Right} from "@element-plus/icons-vue";
|
||||
import {useStore} from "@/store";
|
||||
import {logout} from "@/net";
|
||||
|
||||
defineProps({
|
||||
admin: Boolean
|
||||
})
|
||||
|
||||
const store = useStore()
|
||||
|
||||
function userLogout() {
|
||||
logout(() => router.push("/"))
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="user-info">
|
||||
<el-button type="primary" size="small"
|
||||
@click="router.push('/index')"
|
||||
v-if="admin">
|
||||
回到用户端
|
||||
<el-icon style="margin-left: 5px">
|
||||
<Right/>
|
||||
</el-icon>
|
||||
</el-button>
|
||||
<el-button type="danger" size="small"
|
||||
@click="router.push('/admin')"
|
||||
v-else>
|
||||
前往管理端
|
||||
<el-icon style="margin-left: 5px">
|
||||
<Right/>
|
||||
</el-icon>
|
||||
</el-button>
|
||||
<template/>
|
||||
<div class="profile">
|
||||
<div>{{ store.user.username }}</div>
|
||||
<div>{{ store.user.email }}</div>
|
||||
</div>
|
||||
<el-dropdown>
|
||||
<el-avatar :src="store.avatarUrl"/>
|
||||
<template #dropdown>
|
||||
<el-dropdown-item>
|
||||
<el-icon>
|
||||
<Operation/>
|
||||
</el-icon>
|
||||
个人设置
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item>
|
||||
<el-icon>
|
||||
<Message/>
|
||||
</el-icon>
|
||||
消息列表
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="userLogout" divided>
|
||||
<el-icon>
|
||||
<Back/>
|
||||
</el-icon>
|
||||
退出登录
|
||||
</el-dropdown-item>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.user-info {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
align-items: center;
|
||||
|
||||
.profile {
|
||||
text-align: right;
|
||||
|
||||
:first-child {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
:last-child {
|
||||
font-size: 10px;
|
||||
color: grey;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -53,6 +53,13 @@ const router = createRouter({
|
||||
component: () => import('@/views/settings/PrivacySetting.vue')
|
||||
}
|
||||
]
|
||||
}, {
|
||||
path: '/admin',
|
||||
name: 'admin',
|
||||
component: () => import('@/views/AdminView.vue'),
|
||||
children: [
|
||||
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
|
@ -17,6 +17,9 @@ export const useStore = defineStore('general', {
|
||||
}
|
||||
}
|
||||
}, getters: {
|
||||
isAdmin() {
|
||||
return this.user.role === 'admin'
|
||||
},
|
||||
avatarUrl() {
|
||||
if(this.user.avatar)
|
||||
return `${axios.defaults.baseURL}/images${this.user.avatar}`
|
||||
|
117
my-project-frontend/src/views/AdminView.vue
Normal file
117
my-project-frontend/src/views/AdminView.vue
Normal file
@ -0,0 +1,117 @@
|
||||
<script setup>
|
||||
import {
|
||||
Bell,
|
||||
ChatDotSquare, Collection,
|
||||
DataLine,
|
||||
Document,
|
||||
Files,
|
||||
Location,
|
||||
Monitor, Notification, Position, School,
|
||||
Umbrella,
|
||||
User
|
||||
} from "@element-plus/icons-vue";
|
||||
import UserInfo from "@/components/UserInfo.vue";
|
||||
import {useStore} from "@/store";
|
||||
import {ref} from "vue";
|
||||
import {get} from "@/net";
|
||||
|
||||
const adminMenu = [
|
||||
{
|
||||
title: '校园论坛管理', icon: Location, sub: [
|
||||
{title: '用户管理', icon: User},
|
||||
{title: '帖子广场管理', icon: ChatDotSquare},
|
||||
{title: '失物招领管理', icon: Bell},
|
||||
{title: '校园活动管理', icon: Notification},
|
||||
{title: '表白墙管理', icon: Umbrella},
|
||||
{title: '合作机构管理', icon: School}
|
||||
]
|
||||
}, {
|
||||
title: '探索与发现管理', icon: Position, sub: [
|
||||
{title: '成绩查询管理', icon: Document},
|
||||
{title: '班级课程表管理', icon: Files},
|
||||
{title: '教务通知管理', icon: Monitor},
|
||||
{title: '在线图书馆管理', icon: Collection},
|
||||
{title: '预约教室管理', icon: DataLine}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
const store = useStore()
|
||||
const loading = ref(true)
|
||||
|
||||
get('/api/user/info', (data) => {
|
||||
store.user = data
|
||||
loading.value = false
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="admin-content" v-loading="loading" element-loading-text="正在进入,请稍后...">
|
||||
<el-container style="height: 100%">
|
||||
<el-aside width="230px" class="admin-content-aside">
|
||||
<div style="text-align: center;padding: 15px 0 10px;height: 32px">
|
||||
<el-image class="logo" src="https://element-plus.org/images/element-plus-logo.svg"/>
|
||||
</div>
|
||||
<el-scrollbar style="height: calc(100% - 60px)">
|
||||
<el-menu
|
||||
router
|
||||
:default-active="$route.path"
|
||||
:default-openeds="['1', '2', '3']"
|
||||
style="height: calc(100% - 60px);border-right: none">
|
||||
<el-sub-menu :index="(index + 1).toString()"
|
||||
v-for="(menu, index) in adminMenu">
|
||||
<template #title>
|
||||
<el-icon>
|
||||
<component :is="menu.icon"/>
|
||||
</el-icon>
|
||||
<span><b>{{ menu.title }}</b></span>
|
||||
</template>
|
||||
<el-menu-item :index="subMenu.title" v-for="subMenu in menu.sub">
|
||||
<template #title>
|
||||
<el-icon>
|
||||
<component :is="subMenu.icon"/>
|
||||
</el-icon>
|
||||
{{ subMenu.title }}
|
||||
<el-tag style="margin-left: 10px" size="small"
|
||||
:type="subMenu.tag.type"
|
||||
v-if="subMenu.tag">{{ subMenu.tag.name }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
</el-sub-menu>
|
||||
</el-menu>
|
||||
</el-scrollbar>
|
||||
</el-aside>
|
||||
<el-container>
|
||||
<el-header class="admin-content-header">
|
||||
<div style="flex: 1"></div>
|
||||
<user-info admin/>
|
||||
</el-header>
|
||||
<el-main>Main</el-main>
|
||||
</el-container>
|
||||
</el-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.admin-content {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
.admin-content-aside {
|
||||
border-right: solid 1px var(--el-border-color);
|
||||
|
||||
.logo {
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.admin-content-header {
|
||||
border-bottom: solid 1px var(--el-border-color);
|
||||
height: 55px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
@ -1,20 +1,43 @@
|
||||
<script setup>
|
||||
import {get, logout} from '@/net'
|
||||
import router from "@/router";
|
||||
import {get} from '@/net'
|
||||
import {useStore} from "@/store";
|
||||
import {reactive, ref} from "vue";
|
||||
import {
|
||||
Back,
|
||||
Bell,
|
||||
ChatDotSquare, Check, Collection, DataLine,
|
||||
Document, Files,
|
||||
Location, Lock, Message, Monitor,
|
||||
Location, Lock, Monitor,
|
||||
Notification, Operation,
|
||||
Position,
|
||||
School, Search,
|
||||
Umbrella, User
|
||||
} from "@element-plus/icons-vue";
|
||||
import LightCard from "@/components/LightCard.vue";
|
||||
import UserInfo from "@/components/UserInfo.vue";
|
||||
|
||||
const userMenu = [
|
||||
{ title: '校园论坛', icon: Location, sub: [
|
||||
{ title: '帖子广场', index: '/index', icon: ChatDotSquare },
|
||||
{ title: '失物招领', icon: Bell },
|
||||
{ title: '校园活动', icon: Notification },
|
||||
{ title: '表白墙', icon: Umbrella },
|
||||
{ title: '海文考研', icon: School, tag: { name: '合作机构', type: '' } }
|
||||
]
|
||||
}, {
|
||||
title: '探索与发现', icon: Position, sub: [
|
||||
{ title: '成绩查询', icon: Document },
|
||||
{ title: '班级课程表', icon: Files },
|
||||
{ title: '教务通知', icon: Monitor },
|
||||
{ title: '在线图书馆', icon: Collection },
|
||||
{ title: '预约教室', icon: DataLine }
|
||||
]
|
||||
}, {
|
||||
title: '个人设置', icon: Operation, sub: [
|
||||
{ title: '个人信息设置', icon: User },
|
||||
{ title: '账号安全设置', icon: Lock }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
const store = useStore()
|
||||
const loading = ref(true)
|
||||
@ -25,18 +48,10 @@ const searchInput = reactive({
|
||||
})
|
||||
const notification = ref([])
|
||||
|
||||
get('/api/user/info', (data) => {
|
||||
store.user = data
|
||||
loading.value = false
|
||||
})
|
||||
const loadNotification =
|
||||
() => get('/api/notification/list', data => notification.value = data)
|
||||
loadNotification()
|
||||
|
||||
function userLogout() {
|
||||
logout(() => router.push("/"))
|
||||
}
|
||||
|
||||
function confirmNotification(id, url) {
|
||||
get(`/api/notification/delete?id=${id}`, () => {
|
||||
loadNotification()
|
||||
@ -47,6 +62,11 @@ function confirmNotification(id, url) {
|
||||
function deleteAllNotification() {
|
||||
get(`/api/notification/delete-all`, loadNotification)
|
||||
}
|
||||
|
||||
get('/api/user/info', (data) => {
|
||||
store.user = data
|
||||
loading.value = false
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -72,10 +92,10 @@ function deleteAllNotification() {
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<div class="user-info">
|
||||
<user-info>
|
||||
<el-popover placement="bottom" :width="350" trigger="click">
|
||||
<template #reference>
|
||||
<el-badge style="margin-right: 15px" is-dot :hidden="!notification.length">
|
||||
<el-badge is-dot :hidden="!notification.length">
|
||||
<div class="notification">
|
||||
<el-icon><Bell/></el-icon>
|
||||
<div style="font-size: 10px">消息</div>
|
||||
@ -101,34 +121,7 @@ function deleteAllNotification() {
|
||||
style="width: 100%" plain>清除全部未读消息</el-button>
|
||||
</div>
|
||||
</el-popover>
|
||||
<div class="profile">
|
||||
<div>{{ store.user.username }}</div>
|
||||
<div>{{ store.user.email }}</div>
|
||||
</div>
|
||||
<el-dropdown>
|
||||
<el-avatar :src="store.avatarUrl"/>
|
||||
<template #dropdown>
|
||||
<el-dropdown-item>
|
||||
<el-icon>
|
||||
<Operation/>
|
||||
</el-icon>
|
||||
个人设置
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item>
|
||||
<el-icon>
|
||||
<Message/>
|
||||
</el-icon>
|
||||
消息列表
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="userLogout" divided>
|
||||
<el-icon>
|
||||
<Back/>
|
||||
</el-icon>
|
||||
退出登录
|
||||
</el-dropdown-item>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</user-info>
|
||||
</el-header>
|
||||
<el-container>
|
||||
<el-aside width="230px">
|
||||
@ -138,127 +131,26 @@ function deleteAllNotification() {
|
||||
:default-active="$route.path"
|
||||
:default-openeds="['1', '2', '3']"
|
||||
style="min-height: calc(100vh - 55px)">
|
||||
<el-sub-menu index="1">
|
||||
<template #title>
|
||||
<el-icon>
|
||||
<Location/>
|
||||
</el-icon>
|
||||
<span><b>校园论坛</b></span>
|
||||
</template>
|
||||
<el-menu-item index="/index">
|
||||
<template #title>
|
||||
<el-icon>
|
||||
<ChatDotSquare/>
|
||||
</el-icon>
|
||||
帖子广场
|
||||
</template>
|
||||
</el-menu-item>
|
||||
<el-menu-item>
|
||||
<template #title>
|
||||
<el-icon>
|
||||
<Bell/>
|
||||
</el-icon>
|
||||
失物招领
|
||||
</template>
|
||||
</el-menu-item>
|
||||
<el-menu-item>
|
||||
<template #title>
|
||||
<el-icon>
|
||||
<Notification/>
|
||||
</el-icon>
|
||||
校园活动
|
||||
</template>
|
||||
</el-menu-item>
|
||||
<el-menu-item>
|
||||
<template #title>
|
||||
<el-icon>
|
||||
<Umbrella/>
|
||||
</el-icon>
|
||||
表白墙
|
||||
</template>
|
||||
</el-menu-item>
|
||||
<el-menu-item>
|
||||
<template #title>
|
||||
<el-icon>
|
||||
<School/>
|
||||
</el-icon>
|
||||
海文考研
|
||||
<el-tag style="margin-left: 10px" size="small">合作机构</el-tag>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
</el-sub-menu>
|
||||
<el-sub-menu index="2">
|
||||
<template #title>
|
||||
<el-icon>
|
||||
<Position/>
|
||||
</el-icon>
|
||||
<span><b>探索与发现</b></span>
|
||||
</template>
|
||||
<el-menu-item>
|
||||
<template #title>
|
||||
<el-icon>
|
||||
<Document/>
|
||||
</el-icon>
|
||||
成绩查询
|
||||
</template>
|
||||
</el-menu-item>
|
||||
<el-menu-item>
|
||||
<template #title>
|
||||
<el-icon>
|
||||
<Files/>
|
||||
</el-icon>
|
||||
班级课程表
|
||||
</template>
|
||||
</el-menu-item>
|
||||
<el-menu-item>
|
||||
<template #title>
|
||||
<el-icon>
|
||||
<Monitor/>
|
||||
</el-icon>
|
||||
教务通知
|
||||
</template>
|
||||
</el-menu-item>
|
||||
<el-menu-item>
|
||||
<template #title>
|
||||
<el-icon>
|
||||
<Collection/>
|
||||
</el-icon>
|
||||
在线图书馆
|
||||
</template>
|
||||
</el-menu-item>
|
||||
<el-menu-item>
|
||||
<template #title>
|
||||
<el-icon>
|
||||
<DataLine/>
|
||||
</el-icon>
|
||||
预约教室
|
||||
</template>
|
||||
</el-menu-item>
|
||||
</el-sub-menu>
|
||||
<el-sub-menu index="3">
|
||||
<template #title>
|
||||
<el-icon>
|
||||
<Operation/>
|
||||
</el-icon>
|
||||
<span><b>个人设置</b></span>
|
||||
</template>
|
||||
<el-menu-item index="/index/user-setting">
|
||||
<template #title>
|
||||
<el-icon>
|
||||
<User/>
|
||||
</el-icon>
|
||||
个人信息设置
|
||||
</template>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/index/privacy-setting">
|
||||
<template #title>
|
||||
<el-icon>
|
||||
<Lock/>
|
||||
</el-icon>
|
||||
账号安全设置
|
||||
</template>
|
||||
</el-menu-item>
|
||||
</el-sub-menu>
|
||||
<el-sub-menu :index="(index + 1).toString()"
|
||||
v-for="(menu, index) in userMenu">
|
||||
<template #title>
|
||||
<el-icon>
|
||||
<component :is="menu.icon"/>
|
||||
</el-icon>
|
||||
<span><b>{{ menu.title }}</b></span>
|
||||
</template>
|
||||
<el-menu-item :index="subMenu.index" v-for="subMenu in menu.sub">
|
||||
<template #title>
|
||||
<el-icon>
|
||||
<component :is="subMenu.icon"/>
|
||||
</el-icon>
|
||||
{{ subMenu.title }}
|
||||
<el-tag style="margin-left: 10px" size="small"
|
||||
:type="subMenu.tag.type"
|
||||
v-if="subMenu.tag">{{ subMenu.tag.name }}</el-tag>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
</el-sub-menu>
|
||||
</el-menu>
|
||||
</el-scrollbar>
|
||||
</el-aside>
|
||||
@ -320,9 +212,17 @@ function deleteAllNotification() {
|
||||
|
||||
.logo {
|
||||
height: 32px;
|
||||
width: 340px;
|
||||
text-align: left;
|
||||
|
||||
:deep(.el-image__inner) {
|
||||
width: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
.user-info {
|
||||
gap: 20px;
|
||||
width: 340px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
@ -333,7 +233,6 @@ function deleteAllNotification() {
|
||||
|
||||
.profile {
|
||||
text-align: right;
|
||||
margin-right: 20px;
|
||||
|
||||
:first-child {
|
||||
font-size: 18px;
|
||||
|
Loading…
x
Reference in New Issue
Block a user