vue2.0搭建后台管理

此文章记录了vue2.0搭建一个管理端,其中的路由管理采用前后端配合的模式进行开发。

项目使用vue+elementUI配合开发;

项目中用到了cookie存储,vue的vuex进行状态管理,vue-router等相关技术;

有两种路由开发的模式:

1、纯前端自己管理路由,需要将路由表存放在本地。

        优点:操作简单,减少前后端交互,无需操作数据库;

        缺点:配置不灵活,新增删除路由都要操作前端文件;

2、前后端配合开发。

        优点:配置权限灵活,不用前端改动文件;

        缺点:需要前后端交互;

文件格式:

一、静态配置路由

前端把相关路由对应的页面整理出来,进行路由承载;

router文件夹下的index.js文件,引用vue-router并通过vue.use注册;

import Vue from 'vue';
import Router from 'vue-router';

Vue.use(Router);

export default new Router({
    routes: [
        {
            path: '/',
            component: () => import(/* webpackChunkName: "home" */ '../components/common/Home.vue'),
            meta: { title: 'Home' },
            children: [
                {
                    path: '/customerManagement',
                    component: () =>
                        import(/* webpackChunkName: "customerManagement" */ '../components/view/customerManagement/customerManagement.vue'),
                    meta: { title: '微客管理', class: '微客管理', roles: [], permission: true },
                    name: 'customerManagement'
                },
                //省略....
                {
                    path: '/404',
                    component: () => import(/* webpackChunkName: "404" */ '../components/page/404.vue'),
                    meta: { title: '404' }
                }
            ]
        },
        {
            path: '/login',
            component: () => import(/* webpackChunkName: "login" */ '../components/page/Login.vue'),
            meta: { title: '登录' }
        },
        {
            path: '*',
            redirect: '/404'
        }
    ]
});

二、项目入口,登录页Login.vue

异步调用store中的方法,等返回结果后本地缓存返回的数据;

<template>
    <div class="login-wrap" v-loading.fullscreen.lock="fullscreenLoading" element-loading-text="登录中,请稍后...">
        <div class="login-bg"></div>
        <div class="login-insert">
            <div class="ms-login">
                <el-form :model="param" :rules="rules" ref="login" label-width="0px" class="ms-content">
                    <el-form-item prop="username">
                        <el-input v-model="param.username" placeholder="请输入员工编号" auto-complete="new-password">
                        </el-input>
                    </el-form-item>
                    <el-form-item prop="password">
                        <el-input
                            type="password"
                            placeholder="请输入密码"
                            v-model="param.password"
                            @keyup.enter.native="submitForm()"
                            auto-complete="new-password"
                        >
                        </el-input>
                    </el-form-item>
                    <div class="login-btn">
                        <el-button type="primary" @click="submitForm()">登录</el-button>
                    </div>
                    <p class="login-tips">Tips:请使用chrome浏览器登录。</p>
                </el-form>
            </div>
        </div>
    </div>
</template>

<script>
import { getTree } from '../../api/index';
import { mapState, mapActions } from 'vuex';
export default {
    data: function () {
        return {
            fullscreenLoading: false,
            param: {
                username: '', //admin
                password: '' //admin111
            },
            rules: {
                username: [{ required: true, message: '请输入员工编号', trigger: 'blur' }],
                password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
            }
        };
    },
    mounted() {
        //今入登录页  清除缓存  并进行第一次请求部门
        // this.getTree();
    },
    methods: {
        ...mapActions({
            login: 'auth/loginByUP'
        }),
        submitForm() {
            this.$refs.login.validate((valid) => {
                if (valid) {
                    this.fullscreenLoading = true;
                    let query = {
                        username: this.param.username,
                        password: this.param.password
                    };
                    this.login(query)
                        .then((res) => { //调用store/auth.js中的方法
                            var data = res.data;
                            var code = res.code;
                            if (code === 0) {
                                localStorage.setItem('ms_username', data.user.realName);
                                localStorage.setItem('token', data.token);
                                localStorage.setItem('departmentList', data.user.departmentList);
                                localStorage.setItem('userid', data.user.id);
                                let grade = data.user.grade;
                                if (grade === 0) {
                                    //管理员
                                    this.$router.push('/');
                                } else if (grade === 1) {
                                    //运营
                                    this.$router.push('/');
                                } else if (grade === 2) {
                                    //人资
                                    this.$router.push('/');
                                } else {
                                    this.$router.push('/');
                                }
                                this.getTree();
                            } else {
                                var message = res.message;
                                this.$message.error(message);
                            }
                            this.fullscreenLoading = false;
                        })
                        .catch((err) => {
                            this.fullscreenLoading = false;
                            this.$message.error('网络连接失败,请稍后重试~');
                        });
                } else {
                    this.fullscreenLoading = false;
                    this.$message.error('请输入账号和密码');
                    return false;
                }
            });
        },
        getTree() {
            localStorage.removeItem('scrmTree');//登录的时候  清除缓存
            localStorage.removeItem('scrmStoreTree');//清除门店树
        }
    }
};
</script>

<style lang="scss" scoped>
//登录样式
</style>

 登录接口调用完成后通过this.$route.push进行路由跳转,默认路由表中第一项;

三、进入store/auth.js中进行状态管理

文件位置:auth.js文件位置

import Cookies from 'js-cookie';
import service from '@/utils/request';
import Auth from '@/utils/auth';
import config from '@/utils/config';
import { UsersLogin, RefreshToken } from '@/api/index';
import functionRoute from '@/router/functionList';
//本地权限数组 上线删除
import localRouter from '@/router/localRouter';
var navList_ = Cookies.get('navList');
var username_ = Cookies.get('userName');
var userInfo_ = Cookies.get('userInfo');
if (userInfo_ == '' || userInfo_ == null || userInfo_ == undefined) {
    userInfo_ = {};
}
if (navList_ == '' || navList_ == null || navList_ == undefined || navList_ == '{}') {
    navList_ = [];
}
const state = {
    token: '',
    navList: navList_,
    username: username_,
    userInfo: userInfo_
};
const mutations = {
    setNavList: (state, data) => {
        state.navList = data;
        Cookies.set('navList', data);
    },
    setUsername: (state, data) => {
        state.username = data;
    },
    setUserInfo: (state, data) => {
        state.userInfo = data;
        var grade = data.grade;
        var customer_no = '';
        if (grade == 3) {
            customer_no = data.customer_no;
        }
        var jsons = {
            user_no: data.user_no,
            username: data.username,
            password_status: data.password_status,
            grade: data.grade,
            id: data.id,
            customer_no: customer_no,
            customer_name: data.customer_name
        };
        jsons = JSON.stringify(jsons);
        Cookies.set('userInfo', jsons);
    },
    setToken: (state, data) => {
        if (data) {
            Auth.setToken(data);
            Auth.setLoginStatus();
        } else {
            Auth.removeToken();
            Auth.removeLoginStatus();
        }
        state.token = data;
    }
};
const actions = {
    // 登录
    loginByUP({ commit }, userInfo) {
        var jsons = JSON.stringify(userInfo);
        return new Promise(resolve => {
            UsersLogin({//登录接口
                sfAccount: userInfo.username,
                password: userInfo.password
            })
                .then(res => {
                    var code = res.code;
                    if (code === 0) {
                        var token = res.data.token;
                        var rules = res.data.user.rules;
                        var username = res.data.user.realName;
                        if (rules == '' || rules == null || rules == undefined) {
                            rules = '{}';
                        }
                        console.log('etUserInfo', res.data.user);
                        //通过store进行状态存储
                        commit('setToken', token);
                        commit('setNavList', rules);
                        commit('setUserInfo', res.data.user);
                        commit('setUsername', username);
                        commit('user/setName', username, { root: true });
                    }
                    resolve(res);
                })
                .catch(err => {
                    resolve();
                });
        });
    },
    // 登出
    logout({ commit }) {
        return new Promise(resolve => {
            commit('setToken', '');
            commit('user/setName', '', { root: true });
            commit('tagNav/removeTagNav', '', { root: true });
            resolve();
        });
    },
    // 重新获取用户信息及Token
    // TODO: 这里不需要提供用户名和密码,实际中请根据接口自行修改
    relogin({ dispatch, commit, state }) {
        return new Promise(resolve => {
            // 根据Token进行重新登录
            let token = Cookies.get('token'),
                userName = Cookies.get('userName');
            // 重新登录时校验Token是否存在,若不存在则获取
            if (!token) {
                dispatch('getNewToken').then(() => {
                    commit('setToken', state.token);
                });
            } else {
                commit('setToken', token);
            }
            // 刷新/关闭浏览器再进入时获取用户名
            commit('user/setName', decodeURIComponent(userName), { root: true });
            resolve();
        });
    },
    // 获取新Token
    getNewToken({ commit, state }) {
        return new Promise(resolve => {
            RefreshToken({
                token: state.token
            })
                .then(res => {
                    var statusCode = res.statusCode;
                    if (statusCode == 1) {
                        var token = res.data.token;
                        commit('setToken', token);
                    }
                    resolve();
                })
                .catch(err => {});
        });
    },
    // 获取该用户的菜单列表
    getNavList({ state }) {
        var navList_ = Cookies.get('navList');
        var username_ = Cookies.get('userName');
        if (navList_ == '' || navList_ == null || navList_ == undefined || navList_ == '{}') {
            navList_ = [];
        }
        // return navList_;

        return new Promise(resolve => {
            var formatStr = navList_.replace(/[\\"']/g, '"');
            var arr= eval('(' + formatStr + ')');
            // var arr = JSON.parse(formatStr);
            resolve(arr);
        });
    },
    // 将菜单列表扁平化形成权限列表
    getPermissionList({ state }) {
        return new Promise(resolve => {
            let permissionList = [];
            // 将菜单数据扁平化为一级
            function flatNavList(arr) {
                for (let v of arr) {
                    if (v.subs && v.subs.length) {
                        flatNavList(v.subs);
                    } else {
                        permissionList.push(v);
                    }
                }
            }
            // console.log("cookie中存的格式333", state.navList)
            var formatStr = state.navList.replace(/[\\"']/g, '"');
            var arr= eval('(' + formatStr + ')');
            let navList = arr.items;
            //有接口打开
            navList = navList.concat([
                //原有功能
                ...functionRoute
            ]);
            flatNavList(navList);
            resolve(permissionList);
        });
    }
};

export default {
    namespaced: true,
    state,
    mutations,
    actions
};

四、获取到路由表后渲染侧边栏siddbar.vue

侧边栏文件位置

<template>
    <div class="sidebar" :style="`width:${collapse ? '64' : '200'}px`">
        <div :class="{ logo: true, 'hold-w-l': !collapse, 'hold-w-ls': collapse }">
            <div class="img-box">
                <img :src="logoImgUrl" alt="" />
            </div>
        </div>
        <el-menu
            class="sidebar-el-menu"
            :default-openeds="['1', '2', '3', '4', '5']"
            :default-active="onRoutes"
            :collapse="collapse"
            :unique-opened="false"
            :router="true"
            collapse-transition
        >
            <template v-if="items && items.length > 0">
                <template v-for="item in items">
                    <template v-if="item.subs">
                        <el-submenu :index="item.index" :key="item.index">
                            <template slot="title">
                                <i :class="item.icon"></i>
                                <span slot="title">{{ item.title }}</span>
                            </template>
                            <template v-for="subItem in item.subs">
                                <el-submenu v-if="subItem.subs" :index="subItem.index" :key="subItem.index">
                                    <template slot="title">{{ subItem.title }}</template>
                                    <el-menu-item v-for="(threeItem, i) in subItem.subs" :key="i" :index="threeItem.index">{{
                                        threeItem.title
                                    }}</el-menu-item>
                                </el-submenu>
                                <el-menu-item v-else :index="'/' + subItem.index" :key="subItem.index">{{ subItem.title }}</el-menu-item>
                            </template>
                        </el-submenu>
                    </template>
                    <template v-else>
                        <el-menu-item :index="item.index" :key="item.index">
                            <i :class="item.icon"></i>
                            <span slot="title">{{ item.title }}</span>
                        </el-menu-item>
                    </template>
                </template>
            </template>
        </el-menu>
    </div>
</template>

<script>
import bus from '../common/bus';
import Cookies from 'js-cookie';
import functionRoute from '@/router/functionList';
import localRouter from '@/router/localRouter';
import vLogo from './Logo.vue';
import variables from '@/styles/variables.scss';
export default {
    components: {
        vLogo
    },
    data() {
        return {
            collapse: false,
            logoImgUrl: require('../../assets/img/logo-exe.png')
        };
    },
    computed: {
        variables() {
            return variables;
        },
        onRoutes() {
            // console.log('***************', this.$route.path);
            let fullPath = this.$route.path.split('?')[0];
            fullPath = '/' + fullPath.split('/')[1];
            return fullPath;
        },
        items() {
            var navList = this.$store.state.auth.navList;
            if (navList == '' || navList == null || navList == undefined || navList == '{}') {
                navList = Cookies.get('navList');
            }
            var items_ = eval('(' + navList + ')');
            if (items_ == '' || items_ == null || items_ == undefined || items_ == '{}') {
                items_ = [];
            }
            items_ = items_.items;
            if (items_ && items_.length > 0) {
                return [
                    //有接口打开
                    ...items_
                ];
            } else {
                return [];
            }
        }
    },
    created() {
        // 通过 Event Bus 进行组件间通信,来折叠侧边栏
        bus.$on('collapse', (msg) => {
            this.collapse = msg;
            bus.$emit('collapse-content', msg);
        });
    }
};
</script>

<style lang="scss" scoped>
//样式
</style>

其中el-menu的router属性解释一下:

 开启elementUI中的vue-router模式后,点击侧边栏中的子菜单会根据当前子菜单对应的index的值进行路由跳转。例如:跳转链接http://XXX/#/createTaskMoreChannel其中createTaskMoreChannel就是子菜单对应的index的值。

五、main.js中进行路由守卫

import Vue from 'vue';
import App from './App.vue';
import router from './router';
import ElementUI from 'element-ui';
import VueI18n from 'vue-i18n';
import NProgress from 'nprogress';
import 'nprogress/nprogress.css'; //这个样式必须引入
import {
    messages
} from './components/common/i18n';
import axios from 'axios';
// import 'element-ui/lib/theme-chalk/index.css'; // 默认主题
// import './assets/css/theme-green/index.css'; // 浅绿色主题
import './assets/ele-own/index.css';

import './assets/css/icon.css';
import './components/common/directives';
import 'babel-polyfill';
import VueBus from 'vue-bus';
import store from './store';
import Cookies from 'js-cookie';
import Auth from '@/utils/auth';
import whiteList from '@/router/whiteList';
import md5 from 'js-md5';

NProgress.configure({
    showSpinner: false
}) // NProgress Configuration

Vue.prototype.$axios = axios;
Vue.prototype.$md5 = md5;
Vue.prototype.$bus = new Vue()

Vue.config.productionTip = false;
Vue.use(VueI18n);
Vue.use(VueBus);
Vue.use(ElementUI, {
    size: 'small'
});

const i18n = new VueI18n({
    locale: 'zh',
    messages
});
var permissionList = [];

function initRoute() {
    permissionList = []; //先清空
    return new Promise(resolve => {
        store.dispatch('auth/getNavList').then(res => {
            // console.log("state中的列表", res);
            store.dispatch('auth/getPermissionList').then(res2 => {
                console.log("扁平化列表", res2);
                permissionList = [...res2];
                resolve();
            });
        });
    });
}
// 路由跳转前验证
router.beforeEach((to, from, next) => {
    console.log(to)
    window._axiosPromiseArr.forEach((ele, index) => {
        let reg = /scrm\/dept\/tree/; //部门树 缓存的接口  可以不中止

        let flag = reg.test(ele.url);
        if (!flag) {
            ele.cancel(); //路由跳转之前,清空(终止)上一个页面正在请求的内容
        }
        delete window._axiosPromiseArr[index];
    });
    // NProgress开始进度条
    NProgress.start();
    // 判断用户是否处于登录状态
    //toke判断放在了isLoin里
    if (Auth.isLogin()) {
        // 如果当前处于登录状态,并且跳转地址为login,则自动跳回系统首页
        // 这种情况出现在手动修改地址栏地址时
        if (to.path === '/login') {
            next();
            NProgress.done()
            // next({path: "/defaultText",replace: true})
        } else if (to.path.indexOf('/404') >= 0) {
            // 防止因重定向到error页面造成beforeEach死循环
            next();
        } else if (to.path.indexOf('/403') >= 0) {
            // 防止因重定向到error页面造成beforeEach死循环
            next();
        } else if (to.path.indexOf('/401') >= 0) {
            // 防止因重定向到error页面造成beforeEach死循环
            next();
        } else if (to.path.indexOf('/createTaskNewCallout') >= 0) {
            //新电话外呼任务页面,不设置权限守卫
            // 防止因重定向到error页面造成beforeEach死循环
            next();
        } else {
            initRoute().then(() => {
                let isPermission = false;
                //解决使用query传参
                let fullPath = to.fullPath.split('?')[0]
                fullPath = fullPath.split('/')[1]

                for (var ii = 0; ii < permissionList.length; ii++) {
                    var index = permissionList[ii].index;
                    if (index == fullPath) {
                        isPermission = true;
                        break;
                    }
                }
                /*
                    针对登录以后,刷新根路径的情况:
                    如果登录过了,进行刷新页面,不使用addroutes的情况,判断角色-根路径-跳转哪一页
                */
                // console.log("to.path",to.path,permissionList);

                if (to.path === "/" && permissionList.length > 0) {
                    next({
                        path: permissionList[0].index, //重定向到有权限的第一页
                        replace: true
                    });
                    return
                }
                if (!isPermission) {
                    next({
                        path: '/403',
                        replace: true
                    });
                } else {
                    next();
                }
            });
        }
    } else {
        // 如果是免登陆的页面则直接进入,否则跳转到登录页面
        if (whiteList.indexOf(to.path) >= 0) {
            console.log('该页面无需登录即可访问');
            next();
        } else {
            console.log('当前未处于登录状态,请登录');
            next({
                path: '/login',
                replace: true
            });
        }
    }
});

// 全局后置钩子-常用于结束动画等
router.afterEach(transition => {
    // NProgress结束进度条
    NProgress.done();
});

new Vue({
    router,
    i18n,
    store,
    render: h => h(App)
}).$mount('#app');

总结:项目中的登录用户权限是后端通过对用户指定权限进行表修改,不同用户所看到的页面也不一样。通过前后端分离的模式用户登录之后不同的用户获取到不同的侧边菜单,用户有操作权限的路由通过vuex进行管理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值