增加页面权限
This commit is contained in:
parent
43773ce133
commit
a19b0c48fe
14
README.md
14
README.md
|
|
@ -1,15 +1,17 @@
|
|||
# vue-admin-plus
|
||||
# 基于Vue3.0+TS+Element-plus实现后台管理系统
|
||||
|
||||
## 下载依赖
|
||||
## 安装项目
|
||||
```
|
||||
# 克隆项目
|
||||
git clone -b vue3.0-antdv https://github.com/chuzhixin/vue-admin-better.git
|
||||
# 安装依赖
|
||||
yarn install
|
||||
```
|
||||
|
||||
### 运行
|
||||
```
|
||||
# 本地开发 启动项目
|
||||
yarn serve
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 打包
|
||||
```
|
||||
yarn build
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
"mavon-editor": "^2.10.4",
|
||||
"md-editor-v3": "^1.11.3",
|
||||
"nprogress": "^0.2.0",
|
||||
"path-to-regexp": "^6.2.0",
|
||||
"print-js": "^1.6.0",
|
||||
"vue": "^3.0.0",
|
||||
"vue-cropper": "^1.0.3",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
<template>
|
||||
<el-breadcrumb class="app-breadcrumb" separator="/">
|
||||
<transition-group>
|
||||
<el-breadcrumb-item v-for="(item,index) in obj.levelList" :key="item.path">
|
||||
<span v-if="item.redirect==='noRedirect'||index==obj.levelList.length-1" class="no-redirect">{{ item.meta.title }}</span>
|
||||
<a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
|
||||
</el-breadcrumb-item>
|
||||
</transition-group>
|
||||
</el-breadcrumb>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import pathToRegexp from 'path-to-regexp'
|
||||
import {onMounted, reactive, watch} from "vue";
|
||||
import {useRoute} from 'vue-router'
|
||||
|
||||
const obj = reactive({levelList:{}})
|
||||
const route = useRoute()
|
||||
|
||||
// 获取面包屑
|
||||
const getBreadcrumb = ()=>{
|
||||
let matched = route.matched.filter(item => item.meta && item.meta.title)
|
||||
const first = matched[0]
|
||||
obj.levelList = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
|
||||
}
|
||||
onMounted(()=>{
|
||||
getBreadcrumb()
|
||||
watch(route,()=>{
|
||||
if (route.path.startsWith('/redirect/')) {
|
||||
return
|
||||
}
|
||||
getBreadcrumb()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.app-breadcrumb.el-breadcrumb {
|
||||
margin-left: 10px;
|
||||
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
margin-bottom: 4px;
|
||||
|
||||
|
||||
.no-redirect {
|
||||
cursor: text;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,8 +1,14 @@
|
|||
<template>
|
||||
<section class="app-main">
|
||||
<router-view v-slot="{ Component }">
|
||||
<component :is="Component" />
|
||||
</router-view>
|
||||
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition name="fade-transform" mode="out-in">
|
||||
<div>
|
||||
<component :is="Component" />
|
||||
</div>
|
||||
</transition>
|
||||
</router-view>
|
||||
|
||||
</section>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,9 @@
|
|||
return store.state.permission.routes
|
||||
})
|
||||
|
||||
|
||||
console.log('permission_routes',permission_routes)
|
||||
|
||||
const activeMenu = computed(()=>{
|
||||
const { meta, path } = route
|
||||
// if set path, the sidebar will highlight the path you set
|
||||
|
|
|
|||
|
|
@ -1,10 +1,16 @@
|
|||
<template>
|
||||
<div class="m-layout-header" :style="{left:`${isCollapse?'56':'210'}px`}">
|
||||
<div class="header">
|
||||
<div>
|
||||
<el-icon class="icon" v-if="isCollapse" @click="handleCollapse(false)"><expand /></el-icon>
|
||||
<el-icon class="icon" v-else @click="handleCollapse(true)"><fold/></el-icon>
|
||||
<div class="left">
|
||||
<div>
|
||||
<el-icon class="icon" v-if="isCollapse" @click="handleCollapse(false)"><expand /></el-icon>
|
||||
<el-icon class="icon" v-else @click="handleCollapse(true)"><fold/></el-icon>
|
||||
</div>
|
||||
|
||||
<u-hamburger/>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="right">
|
||||
<u-screen-full/>
|
||||
<el-dropdown @command="commandAction">
|
||||
|
|
@ -31,6 +37,7 @@
|
|||
<script lang="ts" setup>
|
||||
import Personal from './Personal.vue'
|
||||
import TagViews from '../TagsView/index.vue'
|
||||
import UHamburger from "@/components/u-Hamburger/index.vue"
|
||||
import UScreenFull from '@/components/u-screenfull/index.vue'
|
||||
import {computed, ref,} from 'vue'
|
||||
import {useRouter} from 'vue-router'
|
||||
|
|
@ -99,6 +106,10 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
.left{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.right{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
|
|||
|
|
@ -24,13 +24,15 @@ router.beforeEach(async(to, from, next) => {
|
|||
if (to.path === '/login') {
|
||||
// 如果已登录,请重定向到主页
|
||||
next({ path: '/' })
|
||||
NProgress.done()
|
||||
NProgress.done()
|
||||
} else {
|
||||
try {
|
||||
// 路由添加进去了没有及时更新 需要重新进去一次拦截
|
||||
if(hasRoles){
|
||||
const accessRoutes = await store.dispatch('permission/generateRoutes', 'roles')
|
||||
console.log('accessRoutes==',accessRoutes)
|
||||
|
||||
if(!store.state.permission.routes.length){
|
||||
// 获取权限列表进行接口访问 因为这里页面要切换权限
|
||||
// const roles = await store.dispatch('user/getInfo')
|
||||
const accessRoutes = await store.dispatch('permission/generateRoutes', store.getters.roles)
|
||||
hasRoles = false
|
||||
accessRoutes.forEach(item => router.addRoute(item)) // 动态添加访问路由表
|
||||
next({ ...to, replace: true }) // // 这里相当于push到一个页面 不在进入路由拦截
|
||||
|
|
@ -38,8 +40,10 @@ router.beforeEach(async(to, from, next) => {
|
|||
next() // // 如果不传参数就会重新执行路由拦截,重新进到这里
|
||||
}
|
||||
} catch (error) {
|
||||
next(`/login?redirect=${to.path}`)
|
||||
next(`/login?redirect=${to.path}`)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}else{
|
||||
if (whiteList.indexOf(to.path) !== -1) {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import chatRouter from './modules/chat'
|
|||
import componentsRouter from './modules/components'
|
||||
import othersRouter from './modules/other'
|
||||
import externalLink from './modules/externalLink'
|
||||
import permissionRouter from './modules/permission'
|
||||
|
||||
|
||||
interface extendRoute {
|
||||
|
|
@ -31,7 +32,7 @@ export const constantRoutes: Array<RouteRecordRaw&extendRoute> = [
|
|||
path: '/home',
|
||||
component: () => import('@/views/home/index.vue'),
|
||||
name: 'home',
|
||||
meta: { title: '首页', icon: 'film', affix: true }
|
||||
meta: { title: '首页', icon: 'film', affix: true ,role:['other']}
|
||||
},
|
||||
]
|
||||
},
|
||||
|
|
@ -44,7 +45,8 @@ export const asyncRoutes = [
|
|||
chatRouter,
|
||||
componentsRouter,
|
||||
othersRouter,
|
||||
externalLink
|
||||
externalLink,
|
||||
permissionRouter
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -9,26 +9,27 @@ const chartsRouter = {
|
|||
name: 'Charts',
|
||||
meta: {
|
||||
title: '图表',
|
||||
icon: 'trend-charts'
|
||||
icon: 'trend-charts',
|
||||
roles:['other']
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'line',
|
||||
component: () => import('@/views/charts/line.vue'),
|
||||
name: 'line',
|
||||
meta: { title: '折现图', noCache: true }
|
||||
meta: { title: '折现图', noCache: true , roles:['other'] }
|
||||
},
|
||||
{
|
||||
path: 'simple',
|
||||
component: () => import('@/views/charts/simple.vue'),
|
||||
name: 'charts-simple',
|
||||
meta: { title: '简单图表', noCache: true }
|
||||
meta: { title: '简单图表', noCache: true , roles:['other'] }
|
||||
},
|
||||
{
|
||||
path: 'complex',
|
||||
component: () => import('@/views/charts/complex.vue'),
|
||||
name: 'charts-complex',
|
||||
meta: { title: '复杂图表', noCache: true }
|
||||
meta: { title: '复杂图表', noCache: true , roles:['other'] }
|
||||
},
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,26 +9,27 @@ const componentsRouter = {
|
|||
name: 'components',
|
||||
meta: {
|
||||
title: '组件',
|
||||
icon: 'Histogram'
|
||||
icon: 'Histogram',
|
||||
roles:['other']
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'editor',
|
||||
component: () => import('@/views/components-demo/editor.vue'),
|
||||
name: 'editor',
|
||||
meta: { title: '富文本编辑器', noCache: true }
|
||||
meta: { title: '富文本编辑器', noCache: true, roles:['other'] }
|
||||
},
|
||||
{
|
||||
path: 'mark-down',
|
||||
component: () => import('@/views/components-demo/mark-down.vue'),
|
||||
name: 'mark-down',
|
||||
meta: { title: 'markDown', noCache: true }
|
||||
meta: { title: 'markDown', noCache: true , roles:['other']}
|
||||
},
|
||||
{
|
||||
path: 'form',
|
||||
component: () => import('@/views/components-demo/form.vue'),
|
||||
name: 'form',
|
||||
meta: { title: '表单', noCache: true }
|
||||
meta: { title: '表单', noCache: true , roles:['other']}
|
||||
},
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
/** When your routing table is too long, you can split it into small modules**/
|
||||
|
||||
import Layout from "@/layout/index.vue";
|
||||
|
||||
const permissionRouter = {
|
||||
path: '/permission',
|
||||
component: Layout,
|
||||
redirect: 'noRedirect',
|
||||
name: 'permission',
|
||||
meta: {
|
||||
title: '权限测试页',
|
||||
icon: 'trend-charts', roles:['other']
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'page',
|
||||
component: () => import('@/views/permission/page.vue'),
|
||||
name: 'page',
|
||||
meta: { title: '页面权限', noCache: true,icon: 'trend-charts', roles:['other'] }
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
export default permissionRouter
|
||||
|
|
@ -2,6 +2,7 @@ const getters = {
|
|||
permission_routes: state => state.permission.routes,
|
||||
isCollapse: state => state.app.isCollapse,
|
||||
userInfo: state => state.user.userInfo,
|
||||
roles: state => state.user.roles,
|
||||
|
||||
}
|
||||
export default getters
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import {Module} from "vuex";
|
|||
|
||||
import { asyncRoutes, constantRoutes } from '@/router/index'
|
||||
/**
|
||||
* Use meta.role to determine if the current user has permission
|
||||
* 使用 meta.role 来确定当前用户是否具有权限
|
||||
* @param roles
|
||||
* @param route
|
||||
*/
|
||||
|
|
@ -10,12 +10,13 @@ function hasPermission(roles, route) {
|
|||
if (route.meta && route.meta.roles) {
|
||||
return roles.some(role => route.meta.roles.includes(role))
|
||||
} else {
|
||||
return true
|
||||
// return true
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter asynchronous routing tables by recursion
|
||||
* 通过递归过滤异步路由表
|
||||
* @param routes asyncRoutes
|
||||
* @param roles
|
||||
*/
|
||||
|
|
@ -32,6 +33,7 @@ export function filterAsyncRoutes(routes, roles) {
|
|||
}
|
||||
})
|
||||
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
|
|
@ -46,21 +48,22 @@ const mutations = {
|
|||
state.addRoutes = routes
|
||||
state.routes = constantRoutes.concat(routes)
|
||||
|
||||
console.log('===============',state.routes)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
const actions = {
|
||||
generateRoutes({ commit }, roles) {
|
||||
return new Promise(resolve => {
|
||||
// let accessedRoutes
|
||||
// if (roles.includes('admin')) {
|
||||
// accessedRoutes = asyncRoutes || []
|
||||
// } else {
|
||||
// accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
|
||||
// }
|
||||
commit('SET_ROUTES', asyncRoutes)
|
||||
resolve(asyncRoutes)
|
||||
// 在这判断是否有权限,哪些角色拥有哪些权限
|
||||
let accessedRoutes
|
||||
if (roles.includes('admin')) {
|
||||
accessedRoutes = asyncRoutes || []
|
||||
} else {
|
||||
accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
|
||||
}
|
||||
commit('SET_ROUTES', accessedRoutes)
|
||||
resolve(accessedRoutes)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ import {Module} from "vuex";
|
|||
import { getToken, setToken, removeToken } from '@/utils/auth'
|
||||
const state = {
|
||||
token: getToken(),
|
||||
userInfo:localStorage.userInfo?JSON.parse(localStorage.userInfo):{}
|
||||
userInfo:localStorage.userInfo?JSON.parse(localStorage.userInfo):{},
|
||||
roles: localStorage.roles?JSON.parse(localStorage.roles):[],
|
||||
}
|
||||
|
||||
const mutations = {
|
||||
|
|
@ -13,27 +14,40 @@ const mutations = {
|
|||
localStorage.userInfo = JSON.stringify(userInfo)
|
||||
state.userInfo = userInfo
|
||||
},
|
||||
SET_ROLES: (state, roles) => {
|
||||
localStorage.roles = JSON.stringify(roles)
|
||||
state.roles = roles
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const actions = {
|
||||
// 登录
|
||||
login({ commit }, userInfo) {
|
||||
console.log('=========')
|
||||
login({ commit,dispatch }, userInfo) {
|
||||
const { username, password } = userInfo
|
||||
return new Promise((resolve, reject) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
commit('SET_TOKEN', username)
|
||||
|
||||
commit('SET_INFO', userInfo)
|
||||
await dispatch('getInfo', ['admin']) // 获取权限列表 默认就是超级管理员,因为没有进行接口请求 写死
|
||||
setToken(username)
|
||||
resolve(username)
|
||||
})
|
||||
},
|
||||
// 获取用户信息 ,如实际应用中 可以通过token通过请求接口在这里获取用户信息
|
||||
getInfo({ commit, state },roles) {
|
||||
return new Promise((resolve, reject) =>{
|
||||
commit('SET_ROLES', roles)
|
||||
resolve(roles)
|
||||
} )
|
||||
},
|
||||
// 退出
|
||||
logout({ commit, state, dispatch }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
removeToken()
|
||||
commit('SET_TOKEN', '')
|
||||
commit('SET_INFO', '')
|
||||
commit('SET_ROLES', '')
|
||||
resolve(null)
|
||||
})
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
@import './variables.scss';
|
||||
@import './sidebar.scss';
|
||||
@import "./transition.scss";
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
// global transition css
|
||||
|
||||
/* fade */
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.28s;
|
||||
}
|
||||
|
||||
.fade-enter,
|
||||
.fade-leave-active {
|
||||
opacity: 0;
|
||||
}
|
||||
//
|
||||
///* fade-transform */
|
||||
//.fade-transform-leave-active,
|
||||
//.fade-transform-enter-active {
|
||||
// transition: all .2s;
|
||||
//}
|
||||
//
|
||||
//.fade-transform-enter-from {
|
||||
// opacity: 0;
|
||||
// transform: translateX(-30px);
|
||||
//}
|
||||
//
|
||||
//.fade-transform-leave-to {
|
||||
// opacity: 0;
|
||||
// transform: translateX(30px);
|
||||
//}
|
||||
|
||||
/* breadcrumb transition */
|
||||
.breadcrumb-enter-active,
|
||||
.breadcrumb-leave-active {
|
||||
transition: all .2s;
|
||||
}
|
||||
|
||||
.breadcrumb-enter-from,
|
||||
.breadcrumb-leave-active {
|
||||
opacity: 0;
|
||||
//transform: translateX(20px);
|
||||
}
|
||||
|
||||
.breadcrumb-move {
|
||||
transition: all .2s;
|
||||
}
|
||||
|
||||
.breadcrumb-leave-active {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
|
||||
.fade-transform-leave-active,
|
||||
.fade-transform-enter-active {
|
||||
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), border 0s;
|
||||
}
|
||||
|
||||
.fade-transform-enter-from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.fade-transform-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
<div class="name"></div>
|
||||
<div class="description"></div>
|
||||
<div class="list">
|
||||
<div>昵称:有你</div>
|
||||
<div>昵称:小狼</div>
|
||||
<div>职业:前端</div>
|
||||
<div>公司:小公司</div>
|
||||
<div>年龄:1993/09</div>
|
||||
|
|
@ -32,6 +32,9 @@
|
|||
<el-divider></el-divider>
|
||||
<div style="margin-bottom: 15px"><h5>最喜欢的一句话</h5></div>
|
||||
<div>---------- 开心最重要</div>
|
||||
|
||||
<el-divider></el-divider>
|
||||
<div style="margin-bottom: 15px"><h5>如果对你有帮助的话,可以麻烦点一颗 star!</h5></div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
<template>
|
||||
<div class="m-permission-page">
|
||||
<div style="margin-bottom: 20px">
|
||||
权限列表{{store.getters.roles}}
|
||||
</div>
|
||||
<el-radio-group v-model="switchRoles">
|
||||
<el-radio-button label="other" />
|
||||
<el-radio-button label="admin" />
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {computed, ref} from "vue";
|
||||
import {useStore} from "vuex"
|
||||
const store = useStore()
|
||||
|
||||
const switchRoles = computed({
|
||||
get(){
|
||||
return store.getters.roles[0]
|
||||
},
|
||||
set(val){
|
||||
(async ()=>{
|
||||
await store.dispatch("user/getInfo",[val])
|
||||
location.reload()
|
||||
})()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
|
|
@ -6255,6 +6255,11 @@ path-to-regexp@0.1.7:
|
|||
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
|
||||
integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
|
||||
|
||||
path-to-regexp@^6.2.0:
|
||||
version "6.2.0"
|
||||
resolved "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-6.2.0.tgz#f7b3803336104c346889adece614669230645f38"
|
||||
integrity sha512-f66KywYG6+43afgE/8j/GoiNyygk/bnoCbps++3ErRKsIYkGGupyv07R2Ok5m9i67Iqc+T2g1eAUGUPzWhYTyg==
|
||||
|
||||
path-type@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f"
|
||||
|
|
|
|||
Loading…
Reference in New Issue