Commit 119d5ba6 authored by lvweichao's avatar lvweichao

feat: add auth handler

parent 1389ded3
...@@ -6,25 +6,23 @@ ...@@ -6,25 +6,23 @@
<script> <script>
import { fetchCurrentUser } from './service/user'; import { fetchCurrentUser } from './service/user';
import { redirectToLogin } from './utils/util';
function redirectToLogin() {
const loginUrl = '//pandora.yidian-inc.com/tools/admin/login';
const cbUrl = location.href;
location.href = `${loginUrl}?callback=${cbUrl}`;
}
export default { export default {
async mounted() { async mounted() {
if (this.$store.state.permissions && this.$store.status.userInfo) return
const { status, user } = await fetchCurrentUser(); const { status, user } = await fetchCurrentUser();
// {"status":"success","user":{"userid":"732473439","name":"吕伟朝","email":"lvweichao@yidian-inc.com","avatar":""}} // {"status":"success","user":{"userid":"732473439","name":"吕伟朝","email":"lvweichao@yidian-inc.com","avatar":""}}
if (status === 'success') { if (status === 'success') {
this.$store.commit('updateUserInfo', user);
this.$store.dispatch('updateUserPermission', { email: user.email }) this.$store.dispatch('updateUserPermission', { email: user.email })
this.$store.commit('updateUserInfo', user)
} else { } else {
redirectToLogin(); redirectToLogin();
} }
}, },
}; };
</script> </script>
<style lang="less" src="./global.less"></style> <style lang="less" src="./global.less"></style>
...@@ -20,16 +20,16 @@ ...@@ -20,16 +20,16 @@
<script> <script>
import { mapState } from 'vuex' import { mapState } from 'vuex'
import headerConfig from './config'; import { HEADER_CONFIG } from '@/config/pageconfig';
export default { export default {
computed: mapState({ computed: mapState({
userInfo: state => state.userInfo, userInfo: state => (state.userInfo || {}),
}), }),
data() { data() {
return { return {
DEFAULT_AVATAR: headerConfig.miscellaneous.defaultAvatar, DEFAULT_AVATAR: HEADER_CONFIG.miscellaneous.defaultAvatar,
}; };
}, },
}; };
......
const headerConfig = {
appName: 'OP运营管理后台',
logo: 'http://si1.go2yd.com/get-image/0ZAJxXeZ6iu',
menuItems: [
// {
// path: '/home',
// name: '首页',
// },
// {
// path: '/about',
// name: '关于'
// },
{
path: '/enterprise/certification',
name: '企业认证管理'
},
{
path: '/life-no/life',
name: '生活号管理'
},
{
path: '/role',
name: '角色管理'
},
{
path: '/user',
name: '用户管理'
}
],
miscellaneous: {
// 配置当用户头像不存在时使用的fallback头像图URL
defaultAvatar: '//s.go2yd.com/a/thead_meiguoduizhang.png',
},
};
export default headerConfig;
\ No newline at end of file
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
active-text-color="#ffd04b" active-text-color="#ffd04b"
> >
<el-menu-item <el-menu-item
v-for="item in menuItems" v-for="item in menuItemsWithAuth"
:index="item.path" :index="item.path"
:key="item.name" :key="item.name"
> >
...@@ -25,10 +25,12 @@ ...@@ -25,10 +25,12 @@
</template> </template>
<script> <script>
import headerConfig from "./config"; import { HEADER_CONFIG } from "@/config/pageconfig";
import User from './User.vue'; import User from './User.vue';
const { menuItems } = headerConfig; import { getModulePermissions } from '@/utils/authUtil'
const { menuItems } = HEADER_CONFIG;
export default { export default {
name: "PageHeader", name: "PageHeader",
...@@ -38,14 +40,21 @@ export default { ...@@ -38,14 +40,21 @@ export default {
data () { data () {
return { return {
menuItems, menuItems,
basicPath: "/", modulePermissions: []
}; };
}, },
computed: { computed: {
menuItemsWithAuth: function() {
return this.menuItems.filter(ele => this.modulePermissions.includes(ele.key))
},
activeMenu: function () { activeMenu: function () {
return this.$route.path; return this.$route.path;
}, },
}, },
beforeMount() {
this.modulePermissions = getModulePermissions();
},
methods: {}, methods: {},
}; };
</script> </script>
......
<template>
<div class="block">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
v-model:currentPage="currentPage"
:page-size="pageSize"
layout=" total,prev, pager, next"
:total="totalNum"
>
</el-pagination>
</div>
</template>
<script>
export default {
props: ['data', 'totalNum'],
data () {
return {
currentPage: 5,
pageSize: 20
}
},
methods: {
handleSizeChange (size) {
console.log(`每页 ${size} 条`);
this.$emit('update', { pageSize: size })
},
handleCurrentChange (pageIndex) {
console.log(`当前页: ${pageIndex}`);
this.$emit('update', { page: pageIndex })
}
},
}
</script>
<style lang="less">
.block {
margin-top: 50px;
}
</style>
\ No newline at end of file
// 系统顶栏header展示项
export const HEADER_CONFIG = {
appName: 'OP运营管理后台',
logo: 'http://si1.go2yd.com/get-image/0ZAJxXeZ6iu',
menuItems: [
{
path: '/enterprise/certification',
name: '企业认证管理',
key: 'enterprise',
},
{
path: '/life-no/life',
name: '生活号管理',
key: 'lifeNo',
},
{
path: '/role',
name: '角色管理',
key: 'role',
},
{
path: '/user',
name: '用户管理',
key: 'user',
}
],
miscellaneous: {
// 配置当用户头像不存在时使用的fallback头像图URL
defaultAvatar: '//s.go2yd.com/a/thead_meiguoduizhang.png',
},
};
/**
* 模块对应的权限名称。
* key需要与 「HEADER_CONFIG」 中的key对应。
*/
export const PAGEMODULE_PERMISSIONNAME = {
enterprise: 'enterprise_certification_management',
lifeNo: 'life_official_account_management',
role: 'role_management',
user: 'user_management'
}
export const PERMISSIONNAME_PAGEMODULE = (function() {
const obj = {}
for (let ele in PAGEMODULE_PERMISSIONNAME) {
obj[PAGEMODULE_PERMISSIONNAME[ele]] = ele
}
return obj;
})()
/**
* 页面路由对应的权限名称。
* router 用该配置做路由权限拦截。需要增加权限拦截的路由,需在此添加配置。
*/
export const PATH_PERMISSION_NAME = {
'/enterprise/certification': 'enterprise_certification_management',
'/enterprise/audit': 'enterprise_certification_management.audit',
'/enterprise/establish': 'enterprise_certification_management.create',
'/life-no/life': 'life_official_account_management',
'/role': 'role_management',
'/user': 'user_management'
}
\ No newline at end of file
...@@ -4,11 +4,36 @@ import router from './router'; ...@@ -4,11 +4,36 @@ import router from './router';
import store from './store'; import store from './store';
import ElementPlus from 'element-plus'; import ElementPlus from 'element-plus';
// import './assets/styles/index.scss';
import 'element-plus/lib/theme-chalk/index.css'; import 'element-plus/lib/theme-chalk/index.css';
import 'dayjs/locale/zh-cn'; import 'dayjs/locale/zh-cn';
import locale from 'element-plus/lib/locale/lang/zh-cn'; import locale from 'element-plus/lib/locale/lang/zh-cn';
import { fetchCurrentUser } from './service/user';
import { checkPathAuth } from './utils/authUtil';
import { redirectToLogin } from './utils/util';
// 处理路由权限
router.beforeResolve(async (to, from, next) => {
if (to.meta.requireAuth) {
if (!store.state.permissions) {
const { status, user } = await fetchCurrentUser();
if (status === 'success') {
store.commit('updateUserInfo', user);
store.dispatch('updateUserPermission', { email: user.email }).then(() => {
if (!checkPathAuth(to.path)) {
router.push('/403')
}
})
} else {
redirectToLogin();
}
}
next()
} else {
next()
}
})
createApp(App) createApp(App)
.use(router) .use(router)
.use(store) .use(store)
......
<template>
<layout>
<div class="page-about">
<ul class="doc-list">
<li>
<a href="https://v3.vuejs.org/guide/introduction.html" target="_blank"
>Vue3 使用指南</a
>
</li>
<li>
<a
href="https://next.router.vuejs.org/guide/migration/#new-features"
target="_blank"
>Vue Router for Vue3</a
>
</li>
<li>
<a
href="https://github.com/vuejs/vuex/tree/4.0#breaking-changes"
target="_blank"
>
Vuex for Vue3</a
>
</li>
</ul>
<h3><router-link to="/">返回首页</router-link></h3>
</div>
</layout>
</template>
<script>
import Layout from '@/layouts';
export default {
components: {
Layout,
},
};
</script>
<style lang="less">
.page-about {
padding-top: 100px;
text-align: center;
h1,
h2,
h3 {
margin-bottom: 20px;
color: #333;
font-size: 20px;
}
a {
color: #42b983;
font-size: 20px;
text-decoration: underline;
&:hover {
opacity: 0.8;
}
}
.doc-list {
margin: 30px 0;
}
}
</style>
<template> <template>
<div> <div>
<layout> <layout>
homesssss Forbidden!
</layout> </layout>
</div> </div>
</template> </template>
...@@ -15,6 +15,5 @@ ...@@ -15,6 +15,5 @@
}; };
</script> </script>
<style lang="less" src="./index.less"> <style lang="less">
// 样式可以内联,也可以通过 src 引入
</style> </style>
<template>
<div>
<layout>
not found
</layout>
</div>
</template>
<script>
import Layout from '@/layouts';
export default {
components: {
Layout,
},
};
</script>
<style lang="less">
</style>
// 示例样式
.page-home {
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-size: cover;
color: #fff;
.logo {
width: 60px;
height: 60px;
}
.title {
margin-top: 10px;
font-size: 22px;
}
.desp {
margin: 10px 5% 0;
}
a {
color: #fff;
text-decoration: underline;
}
}
<template>
<layout>
<div class="user">
<el-form :inline="true" :model="formInline" class="demo-form-inline">
<el-form-item label="生活号名称/企业名称">
<el-input
class="search_life"
maxlength="15"
v-model="formInline.user"
placeholder="生活号名称/企业名称"
></el-input>
</el-form-item>
<el-form-item label="类型">
<el-select v-model="formInline.region" placeholder="类型">
<el-option value="shanghai"></el-option>
<el-option value="beijing"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSearchSubmit">查询</el-button>
<el-button @click="onReset">重置</el-button>
</el-form-item>
</el-form>
<el-table
:data="tableData"
border
style="width: 100%"
@row-click="toLifeDetail"
>
<el-table-column prop="id" label="ID"> </el-table-column>
<el-table-column prop="name" label="用户姓名"> </el-table-column>
<el-table-column prop="address" label="用户账号"> </el-table-column>
<el-table-column prop="name" label="状态"> </el-table-column>
<el-table-column prop="date" label="手机号"> </el-table-column>
<el-table-column prop="date" label="最后修改日期"> </el-table-column>
<el-table-column prop="date" label="最后修改人账号"> </el-table-column>
</el-table>
<page :totalNum="totalNum" @update="update" />
</div>
</layout>
</template>
<script>
import Layout from '@/layouts'
import page from "@/components/Pagination"
export default {
components: {
Layout,
page
},
data () {
return {
params: {
page: 1,
pageSize: 10
},
totalNum: 1000,
lifeList: [],
tableData: [{
id: '001',
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
id: '002',
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀区金沙江路 1517 弄'
}, {
id: '003',
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄'
}, {
id: '004',
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1516 弄'
}],
formInline: {
user: '',
region: ''
}
}
},
methods: {
//查询
onSearchSubmit () {
console.log(this.formInline.user, this.formInline.region, 'submit!');
},
//重置
onReset () {
this.formInline.user = "",
this.formInline.region = ""
},
update (obj) {
Object.assign(this.params, obj)
this.getlifeNoList(this.params)
},
toLifeDetail (row) {
// this.$router.push({ path: lifeNoDetail })
console.log(row.id, 'eee去详情')
},
//获取列表数据
getlifeNoList () {
}
}
}
</script>
<style lang="less" scoped>
.life-no {
margin: 30px;
.search_life {
width: 280px;
}
}
</style>
<template>
<div class="user-detail">
<el-descriptions class="margin-top" :column="3" :size="size" border>
<el-descriptions-item>
<template #label> 用户姓名 </template>
kooriookami
</el-descriptions-item>
<el-descriptions-item>
<template #label> 手机号 </template>
18100000000
</el-descriptions-item>
<el-descriptions-item>
<template #label> 所属组织 </template>
研发部
</el-descriptions-item>
<el-descriptions-item>
<template #label> 添加人账号 </template>
XXXX
</el-descriptions-item>
<el-descriptions-item>
<template #label> 添加时间 </template>
2020-12-01
</el-descriptions-item>
<el-descriptions-item>
<template #label> 最后修改人账号 </template>
张三
</el-descriptions-item>
<el-descriptions-item>
<template #label> 最后修改时间 </template>
2021-05-21
</el-descriptions-item>
<el-descriptions-item>
<template #label> 已拥有角色池 </template>
角色池
</el-descriptions-item>
<el-descriptions-item>
<template #label> 角色名称 </template>
管理员
</el-descriptions-item>
<el-descriptions-item>
<template #label> 敏感词权限 </template>
敏感词权限
</el-descriptions-item>
<el-descriptions-item>
<template #label> 数据权限 </template>
数据权限
</el-descriptions-item>
</el-descriptions>
</div>
</template>
<script>
export default {
data () {
return {
userInfoList: [{
}]
}
}
}
</script>
<style lang="less" scoped>
.user-detail {
margin: 50px;
}
</style>
\ No newline at end of file
import { createRouter, createWebHistory } from "vue-router"; import { createRouter, createWebHistory } from "vue-router";
import Home from "../pages/Home";
import Login from "../pages/Login";
import Certification from '@/pages/Enterprise/Certification';
import LifeNo from '../pages/Life-no/index.vue' import LifeNo from '../pages/Life-no/index.vue'
import LifeNoDetail from '../pages/Life-no/life-no-detail.vue' import LifeNoDetail from '../pages/Life-no/life-no-detail.vue'
import User from '../pages/User/userList'
import UserDetail from '../pages/User/user-detail.vue' import UserDetail from '../pages/User/user-detail.vue'
import AddRole from '../pages/Role/add-role.vue'
import AddRole from '../pages/Role/add-role.vue'
const routes = [ const routes = [
{ {
path: "/home", path: "/",
name: "Home", redirect: '/enterprise/certification'
component: Home,
},
{
path: "/login",
name: "Login",
component: Login,
}, },
{ {
path: "/about", path: '/404',
name: "About", name: 'NotFound',
component: () => import(/* webpackChunkName: "about" */ "../pages/About"), component: () => import(/* webpackChunkName: "enterprise" */ '@/pages/Catch/notFound'),
}, },
{ {
path: "/", path: '/403',
name: "Home", name: 'Forbidden',
component: Certification, component: () => import(/* webpackChunkName: "enterprise" */ '@/pages/Catch/forbidden'),
}, },
{ {
path: '/enterprise/certification', path: '/enterprise/certification',
name: 'Certification', name: 'Certification',
component: Certification, component: () => import(/* webpackChunkName: "enterprise" */ '@/pages/Enterprise/Certification'),
// component: () => import(/* webpackChunkName: "enterprise" */ '@/pages/Enterprise/Certification') meta: {
requireAuth: true,
},
}, },
{ {
path: '/enterprise/audit', path: '/enterprise/audit',
name: 'Audit', name: 'Audit',
component: () => import(/* webpackChunkName: "enterprise" */ '@/pages/Enterprise/Audit') component: () => import(/* webpackChunkName: "enterprise" */ '@/pages/Enterprise/Audit'),
meta: {
requireAuth: true,
}
}, },
{ {
path: '/enterprise/establish', path: '/enterprise/establish',
name: 'Establish', name: 'Establish',
component: () => import(/* webpackChunkName: "enterprise" */ '@/pages/Enterprise/Establish') component: () => import(/* webpackChunkName: "enterprise" */ '@/pages/Enterprise/Establish'),
meta: {
requireAuth: true,
}
}, },
//生活号管理 //生活号管理
{ {
path: '/life-no/life', path: '/life-no/life',
name: 'LifeNo', name: 'LifeNo',
component: LifeNo, component: LifeNo,
meta: {
requireAuth: true,
}
}, },
{ {
path: '/life-no/lifeNoDetail', path: '/life-no/lifeNoDetail',
...@@ -59,6 +62,14 @@ const routes = [ ...@@ -59,6 +62,14 @@ const routes = [
component: LifeNoDetail, component: LifeNoDetail,
}, },
//用户管理 //用户管理
{
path: '/user',
name: 'User',
component: User,
meta: {
requireAuth: true,
}
},
{ {
path: '/user/userDetail', path: '/user/userDetail',
name: 'UserDetail', name: 'UserDetail',
......
...@@ -3,8 +3,8 @@ import { getPermissions } from '@/service/user'; ...@@ -3,8 +3,8 @@ import { getPermissions } from '@/service/user';
export default createStore({ export default createStore({
state: { state: {
permissions: [], permissions: null,
userInfo: {}, userInfo: null,
}, },
mutations: { mutations: {
updateUserPermission(state, payload) { updateUserPermission(state, payload) {
...@@ -22,7 +22,5 @@ export default createStore({ ...@@ -22,7 +22,5 @@ export default createStore({
} }
}, },
modules: {}, modules: {},
getters: { getters: {}
}
}); });
// 包含权限相关的工具方法,注意要在vue的生命周期中调用这些方法,否则会有获取不到权限的问题
import store from '../store'
import { PAGEMODULE_PERMISSIONNAME, PERMISSIONNAME_PAGEMODULE, PATH_PERMISSION_NAME } from '../config/pageconfig'
function getPermissionObj() {
// 支持到二级权限
const permissions = store.state.permissions || [];
const authObj = {};
permissions.forEach(ele => {
authObj[ele.name] = {};
if (ele.sub_permissions) {
ele.sub_permissions.forEach(sp => {
authObj[ele.name][sp.name] = true;
})
}
})
return authObj;
}
/**
* 检查页面路径是否有权限
* @param {*} path
* @returns
*/
export function checkPathAuth(path) {
const authObj = getPermissionObj();
if (path in PATH_PERMISSION_NAME) {
const authArr = PATH_PERMISSION_NAME[path].split('.');
const [p, subP] = authArr;
if (p && subP) {
return !!authObj[p][subP];
} else if (p) {
return !!authObj[p];
}
return false;
}
return true;
}
/**
* 获取页面子权限
* @param {*} module: 枚举值参考:PAGEMODULE_PERMISSIONNAME key
* @returns 自权限列表
*/
export function getModuleSubPermissions(module) {
const authObj = getPermissionObj();
return Object.keys(authObj[PAGEMODULE_PERMISSIONNAME[module]])
}
/**
* 获取一级权限
* @returns 一级权限列表
*/
export function getModulePermissions() {
const authObj = getPermissionObj();
const modules = Object.keys(authObj).map(ele => PERMISSIONNAME_PAGEMODULE[ele])
return modules.filter(ele => !!ele)
}
\ No newline at end of file
// export async function handleUserLogin() {
// const { status, user } = await fetchCurrentUser();
// // {"status":"success","user":{"userid":"732473439","name":"吕伟朝","email":"lvweichao@yidian-inc.com","avatar":""}}
// if (status === 'success') {
// store.commit('updateUserInfo', user);
// store.dispatch('updateUserPermission', { email: user.email }).then(() => {
// router.beforeResolve((to, from, next) => {
// if (to.meta.requireAuth) {
// console.log(33333, to)
// console.log(5555, checkPathAuth(to.path));
// next()
// } else {
// next()
// }
// })
// });
// } else {
// redirectToLogin();
// }
// }
export function redirectToLogin() {
const loginUrl = '//pandora.yidian-inc.com/tools/admin/login';
const cbUrl = location.href;
location.href = `${loginUrl}?callback=${cbUrl}`;
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment