新版security demo(二)前端

写这篇博客,刚好换了台电脑,那就借着这个demo复习下VUE环境的搭建。

一、前端项目搭建

1、安装node

官网下载安装即可。

2、安装脚手架
复制代码
npm install -g vue-cli

使用脚手架搭建一个demo前端项目

复制代码
vue init webpack 项目名称
3、安装依赖

这里安装了用到的element、jsonp等。

复制代码
cnpm  i  element-ui -S

npm install vue-jsonp --save

npm install axios

npm install nprogress --save

npm install js-cookie

 npm install --save vuex

完整的package.json依赖文件: (

复制代码
{
  "name": "demo",
  "version": "1.0.0",
  "description": "A Vue.js project",
  "author": "wtyy",
  "private": true,
  "scripts": {
    "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
    "start": "npm run dev",
    "build": "node build/build.js"
  },
  "dependencies": {
    "axios": "0.17.1",
    "crypto-js": "^4.0.0",
    "echarts": "^4.9.0",
    "element-ui": "2.3.4",
    "js-cookie": "2.2.0",
    "nprogress": "0.2.0",
    "vue": "2.5.10",
    "vue-bus": "^1.2.1",
    "vue-jsonp": "^0.1.8",
    "vue-router": "3.0.1",
    "vuedraggable": "^2.24.3",
    "vuex": "3.0.1",
    "vuex-persist": "^2.2.0"
  },
  "devDependencies": {
    "autoprefixer": "^7.1.2",
    "babel-core": "^6.22.1",
    "babel-helper-vue-jsx-merge-props": "^2.0.3",
    "babel-loader": "^7.1.1",
    "babel-plugin-syntax-jsx": "^6.18.0",
    "babel-plugin-transform-runtime": "^6.22.0",
    "babel-plugin-transform-vue-jsx": "^3.5.0",
    "babel-preset-env": "^1.3.2",
    "babel-preset-stage-2": "^6.22.0",
    "chalk": "^2.0.1",
    "copy-webpack-plugin": "^4.0.1",
    "css-loader": "^0.28.0",
    "extract-text-webpack-plugin": "^3.0.0",
    "file-loader": "^1.1.4",
    "friendly-errors-webpack-plugin": "^1.6.1",
    "html-webpack-plugin": "^2.30.1",
    "node-notifier": "^5.1.2",
    "optimize-css-assets-webpack-plugin": "^3.2.0",
    "ora": "^1.2.0",
    "portfinder": "^1.0.13",
    "postcss-import": "^11.0.0",
    "postcss-loader": "^2.0.8",
    "postcss-url": "^7.2.1",
    "rimraf": "^2.6.0",
    "semver": "^5.3.0",
    "shelljs": "^0.7.6",
    "uglifyjs-webpack-plugin": "^1.1.1",
    "url-loader": "^0.5.8",
    "vue-loader": "^13.3.0",
    "vue-style-loader": "^3.0.1",
    "vue-template-compiler": "2.5.10",
    "webpack": "^3.6.0",
    "webpack-bundle-analyzer": "^2.9.0",
    "webpack-dev-server": "^2.9.1",
    "webpack-merge": "^4.1.0"
  },
  "engines": {
    "node": ">= 6.0.0",
    "npm": ">= 3.0.0"
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not ie <= 8"
  ]
}

二、项目代码

1、环境

主要配置后端接口地址、前端端口号

(1)config/dev.env.js

复制代码
'use strict'
const merge = require('webpack-merge')
const prodEnv = require('./prod.env')

module.exports = merge(prodEnv, {
  NODE_ENV: '"development"',
  BASE_API: '"http://localhost:2222/securityDemo/"',
})

(2)test.env.js

复制代码
'use strict'
const merge = require('webpack-merge')
const prodEnv = require('./prod.env')

module.exports = merge(prodEnv, {
  NODE_ENV: '"development"',
  BASE_API: '"http://localhost:2222/securityDemo/"',
})

(3)prod.env.js

复制代码
'use strict'
module.exports = {
  NODE_ENV: '"production"',
  BASE_API: '"http://xxx.com/mydemo/"',
}

(4)index

复制代码
'use strict'
// Template version: 1.2.6
// see http://vuejs-templates.github.io/webpack for documentation.

const path = require('path')

module.exports = {
  dev: {

    // Paths
    assetsSubDirectory: 'static',
    assetsPublicPath: '/',
    proxyTable: {},

    // Various Dev Server settings
    host: 'localhost', // can be overwritten by process.env.HOST
    port: 9528, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
    autoOpenBrowser: true,
    errorOverlay: true,
    notifyOnErrors: false,
    poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-

    // Use Eslint Loader?
    // If true, your code will be linted during bundling and
    // linting errors and warnings will be shown in the console.
    useEslint: true,
    // If true, eslint errors and warnings will also be shown in the error overlay
    // in the browser.
    showEslintErrorsInOverlay: false,

    /**
     * Source Maps
     */
    // https://webpack.js.org/configuration/devtool/#development
    devtool: 'cheap-source-map',

    // If you have problems debugging vue-files in devtools,
    // set this to false - it *may* help
    // https://vue-loader.vuejs.org/en/options.html#cachebusting
    cacheBusting: true,

    // CSS Sourcemaps off by default because relative paths are "buggy"
    // with this option, according to the CSS-Loader README
    // (https://github.com/webpack/css-loader#sourcemaps)
    // In our experience, they generally work as expected,
    // just be aware of this issue when enabling this option.
    cssSourceMap: false,
  },

  test: {
    env: require('./test.env'),
    // Template for index.html
    index: path.resolve(__dirname, '../dist/index.html'),

    // Paths
    assetsRoot: path.resolve(__dirname, '../dist'),
    assetsSubDirectory: 'static',

    /**
     * You can set by youself according to actual condition
     * You will need to set this if you plan to deploy your site under a sub path,
     * for example GitHub pages. If you plan to deploy your site to https://foo.github.io/bar/,
     * then assetsPublicPath should be set to "/bar/".
     * In most cases please use '/' !!!
     */
    assetsPublicPath: 'http://test.xxx.com/mydemo/', // If you are deployed on the root path, please use '/'

    /**
     * Source Maps
     */
    productionSourceMap: false,
    // https://webpack.js.org/configuration/devtool/#production
    devtool: '#source-map',

    // Gzip off by default as many popular static hosts such as
    // Surge or Netlify already gzip all static assets for you.
    // Before setting to `true`, make sure to:
    // npm install --save-dev compression-webpack-plugin
    productionGzip: false,
    productionGzipExtensions: ['js', 'css'],

    // Run the build command with an extra argument to
    // View the bundle analyzer report after build finishes:
    // `npm run build --report`
    // Set to `true` or `false` to always turn it on or off
    bundleAnalyzerReport: process.env.npm_config_report
  },
  build: {
    env: require('./prod.env'),
    // Template for index.html
    index: path.resolve(__dirname, '../dist/index.html'),

    // Paths
    assetsRoot: path.resolve(__dirname, '../dist'),
    assetsSubDirectory: 'static',

    /**
     * You can set by youself according to actual condition
     * You will need to set this if you plan to deploy your site under a sub path,
     * for example GitHub pages. If you plan to deploy your site to https://foo.github.io/bar/,
     * then assetsPublicPath should be set to "/bar/".
     * In most cases please use '/' !!!
     */
    assetsPublicPath: 'http://xxx.com/mydemo/',

    /**
     * Source Maps
     */
    productionSourceMap: false,
    // https://webpack.js.org/configuration/devtool/#production
    devtool: '#source-map',

    // Gzip off by default as many popular static hosts such as
    // Surge or Netlify already gzip all static assets for you.
    // Before setting to `true`, make sure to:
    // npm install --save-dev compression-webpack-plugin
    productionGzip: false,
    productionGzipExtensions: ['js', 'css'],

    // Run the build command with an extra argument to
    // View the bundle analyzer report after build finishes:
    // `npm run build --report`
    // Set to `true` or `false` to always turn it on or off
    bundleAnalyzerReport: process.env.npm_config_report
  }
}
2、util

src下新建utils目录,封装工具类

(1)auth.js封装token操作方法
复制代码
import Cookies from 'js-cookie'

const TokenKey = 'Admin-Token'

export function getToken() {
  return Cookies.get(TokenKey)
}

export function setToken(token) {
  return Cookies.set(TokenKey, token)
}

export function removeToken() {
  return Cookies.remove(TokenKey)
}
(2)request.js封装axois请求
复制代码
import axios from 'axios'
// 配置前端跨域
axios.defaults.withCredentials = true
import { Message, MessageBox } from 'element-ui'
import store from '../store'
import { getToken } from '@/utils/auth'

// 创建axios实例
const service = axios.create({
  baseURL: process.env.BASE_API, // api的base_url
  timeout: 5000 // 请求超时时间
})

// request拦截器
service.interceptors.request.use(config => {
  if (store.getters.token) {
    config.headers['token'] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
  }
  return config
}, error => {
  // Do something with request error
  console.log(error) // for debug
  Promise.reject(error)
})

// respone拦截器
service.interceptors.response.use(
  response => {
    /**
     * code为非20000是抛错 可结合自己业务进行修改
     */
    const res = response.data
    if (res.code !== 200 && res.code !=300) {
      Message({
        message: res.message,
        type: 'error',
        duration: 5 * 1000
      })

      // 401 token失效
      if (res.code === 401) {
        // MessageBox.confirm('你已被登出,可以取消继续留在该页面,或者重新登录', '确定登出', {
        //   confirmButtonText: '重新登录',
        //   cancelButtonText: '取消',
        //   type: 'warning'
        // }).then(() => {
        //   store.dispatch('FedLogOut').then(() => {
        //     location.reload()// 为了重新实例化vue-router对象 避免bug
        //   })
        // })
        store.dispatch('FedLogOut').then(() => {
          location.reload()// 为了重新实例化vue-router对象 避免bug
        })
      }
      return Promise.reject('error')
    } else {
      return response.data
    }
  },
  error => {
    console.log('err' + error)// for debug
    Message({
      message: error.message,
      type: 'error',
      duration: 5 * 1000
    })
    return Promise.reject(error)
  }
)

export default service
3、api

src下新增api目录,封装后台接口调用

(1)login.js

复制代码
import request from '@/utils/request'

//获取验证码
export function getCode(){
  return request({
    url: '/code/getCode',
    method: 'get'
  })
}

//登录
export function login(user) {
  return request({
    url: '/login',
    method: 'post',
    datatype:'application/json',
    //data:user
    params:{
      "userName":user.userName,
      "passWord": user.password
    }
  })
}


//获取我的权限列表
export function getMyAuthorities() {
  return request({
    url: '/user/getCurrentUser',
    method: 'post'
  })
}


//退出登录
export function logout() {
  return request({
    url: '/user/logout',
    method: 'get'
  })
}

(2)user.js:

复制代码
import request from '@/utils/request'

export function getAllUsers() {
  return request({
    url: '/user/getAllUsers',
    method: 'get'
  })
}
4、store

src下新增store目录,store下按照以下示例新建文件

(1)index.js:

复制代码
import Vue from 'vue'
import Vuex from 'vuex'
import app from './modules/app'
import permission from './modules/permission'
import user from './modules/user'
import getters from './getters'

Vue.use(Vuex)

const store = new Vuex.Store({
  modules: {
    app,
    permission,
    user
  },
  getters
})

export default store

(2)getters.js:

复制代码
const getters = {
  sidebar: state => state.app.sidebar,
  //token
  token: state => state.user.token,
  //用户名
  name: state => state.user.name,
  //角色
  roles: state => state.user.roles,
  //后台返回的权限code
  authorities:state =>state.user.authorities,
  //动态权限路由
  permission_routers: state => state.permission.routers,
  //固定权限路由
  addRouters: state => state.permission.addRouters
}
export default getters

(3)/mudules/app.js

复制代码
import Cookies from 'js-cookie'

const app = {
  state: {
    sidebar: {
      opened: !+Cookies.get('sidebarStatus'),
      withoutAnimation: false
    },
    device: 'desktop'
  },
  mutations: {
    TOGGLE_SIDEBAR: state => {
      if (state.sidebar.opened) {
        Cookies.set('sidebarStatus', 1)
      } else {
        Cookies.set('sidebarStatus', 0)
      }
      state.sidebar.opened = !state.sidebar.opened
      state.sidebar.withoutAnimation = false
    },
    CLOSE_SIDEBAR: (state, withoutAnimation) => {
      Cookies.set('sidebarStatus', 1)
      state.sidebar.opened = false
      state.sidebar.withoutAnimation = withoutAnimation
    },
    TOGGLE_DEVICE: (state, device) => {
      state.device = device
    }
  },
  actions: {
    ToggleSideBar: ({ commit }) => {
      commit('TOGGLE_SIDEBAR')
    },
    CloseSideBar({ commit }, { withoutAnimation }) {
      commit('CLOSE_SIDEBAR', withoutAnimation)
    },
    ToggleDevice({ commit }, device) {
      commit('TOGGLE_DEVICE', device)
    }
  }
}

export default app

(4)/mudules/permission.js

复制代码
import { asyncRouterMap, constantRouterMap } from '@/router/index'

/**
 * 通过meta.authority判断是否与当前用户权限匹配
 * @param authorities
 * @param route
 */
function hasPermission(authorities, route) {
  if (route.meta && route.meta.authority) {
    return authorities.some(authority => route.meta.authority.indexOf(authority) >= 0)
  } else {
    return true
  }
}

/**
 * 递归过滤异步路由表,返回符合用户角色权限的路由表
 * @param asyncRouterMap
 * @param authorities
 */
function filterAsyncRouter(asyncRouterMap, authorities) {
  const accessedRouters = asyncRouterMap.filter(route => {
    if (hasPermission(authorities, route)) {
      if (route.children && route.children.length) {
        route.children = filterAsyncRouter(route.children, authorities)
      }
      return true
    }
    return false
  })
  return accessedRouters
}

const permission = {
  state: {
    routers: constantRouterMap,
    addRouters: []
  },
  mutations: {
    SET_ROUTERS: (state, routers) => {
      state.addRouters = routers
      state.routers = constantRouterMap.concat(routers)
    }
  },
  actions: {
    GenerateRoutes({ commit }, data) {
      return new Promise(resolve => {
        const { authorities } = data
        let accessedRouters
        if (authorities.indexOf('admin') >= 0) {
          accessedRouters = asyncRouterMap
        } else {
          accessedRouters = filterAsyncRouter(asyncRouterMap, authorities)
        }
        commit('SET_ROUTERS', accessedRouters)
        resolve()
      })
    }
  }
}

export default permission

(5)/mudules/user.js

复制代码
import { login, logout, getMyAuthorities } from '@/api/login'
import { getToken, setToken, removeToken } from '@/utils/auth'

const user = {
  state: {
    token: getToken(),
    name: '',
    authorities: [],
    roles: []
  },

  mutations: {
    SET_TOKEN: (state, token) => {
      state.token = token
    },
    SET_NAME: (state, name) => {
      state.name = name
    },
    SET_PERMISSION: (state, authorities) => {
      state.authorities = authorities
    },
    SET_ROLES: (state, roles) => {
      state.roles = roles
    }
  },

  actions: {
    // 登录
    Login({ commit }, user) {
      const userName = user.userName
      const pwd = user.password
      return new Promise((resolve, reject) => {
        login(user).then(response => {
          const data = response.data
          setToken(data)
          commit('SET_TOKEN', data)
          resolve()
        }).catch(error => {
          reject(error)
        })
      })
    },

    // 获取用户权限信息
    GetInfo({ commit, state }) {
      return new Promise((resolve, reject) => {
        getMyAuthorities().then(response => {
          console.info('res' + response)
          const data = response.data.menus
          var permissions = []
          data.forEach(item=>{
            permissions.push(item);
          })
          debugger
          commit('SET_PERMISSION', permissions)

          resolve(permissions)
        }).catch(error => {
          reject(error)
        })
      })
    },


    // 登出
    LogOut({ commit, state }) {
      return new Promise((resolve, reject) => {
        logout(state.token).then(() => {
          commit('SET_TOKEN', '')
          commit('SET_PERMISSION', [])
          removeToken()
          logout().then(response=>{})
          resolve()
        }).catch(error => {
          reject(error)
        })
      })
    },

    // 前端 登出
    FedLogOut({ commit }) {
      return new Promise(resolve => {
        commit('SET_TOKEN', '')
        removeToken()
        resolve()
      })
    }
  }
}

export default user
5、router
复制代码
asyncCodeMenu暂时没有用到,是准备存放各模块及其详情页路由的(避免index写的过长)。这里只贴index.js代码:
复制代码
import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)



export const constantRouterMap = [
 // { path: '/404', component: () => import('@/views/404'), hidden: true },
  { path: '/login', component: () => import('@/views/login/index'), hidden: true },

      // {
      //   path: 'mytest41',
      //   name: 'mytest41',
      //   component: () => import('@/views/mytest/mytest4/mytest41')
      //
      // },
      // {
      //   path: 'mytest42',
      //   name: 'mytest42',
      //   component: () => import('@/views/mytest/mytest4/mytest42')
      //
      // },
      // {
      //   path: 'mytest51',
      //   name: 'mytest51',
      //   component: () => import('@/views/mytest/mytest5/mytest51')
      //
      // },
      // {
      //   path: 'mytest521',
      //   name: 'mytest521',
      //   component: () => import('@/views/mytest/mytest5/mytest521')
      //
      // },
      // {
      //   path: 'mytest522',
      //   name: 'mytest522',
      //   component: () => import('@/views/mytest/mytest5/mytest522')
      //
      // }


  // {
  //   path: '/',
  //   component: Layout,
  //   redirect: '/dashboard',
  //   name: '首页',
  //   icon: '首页',
  //   hidden: true,
  //   children: [{
  //     path: '/dashboard',
  //     component: () => import('@/views/dashboard/index')
  //   }]
  // }
]
/**
 * hidden: true                   if `hidden:true` will not show in the sidebar(default is false)
 * alwaysShow: true               if set true, will always show the root menu, whatever its child routes length
 *                                if not set alwaysShow, only more than one route under the children
 *                                it will becomes nested mode, otherwise not show the root menu
 * redirect: noredirect           if `redirect:noredirect` will no redirct in the breadcrumb
 * name:'router-name'             the name is used by <keep-alive> (must set!!!)
 * meta : {
 title: 'title'               the name show in submenu and breadcrumb (recommend set)
 icon: 'svg-name'             the icon show in the sidebar,
 }
 **/
export const asyncRouterMap = [
  {
    path: '/mytest',
    name: 'mytest',
    hidden:true,
    component: () => import('@/views/mytest/index'),
    children: [
      {
        path: '/main',
        redirect: 'main'
      },
      {
        path: '/mytest/main',
        name: 'main',
        component: () => import('@/views/mytest/main'),
      },

      {
        path: '/mytest/usermanage',
        name: 'userManage',
        component: () => import('@/views/mytest/usermanage'),
      },

      {
        path: '/mytest/rolemanage',
        name: 'rolemanage',
        component: () => import('@/views/mytest/rolemanage'),
        //meta: { title: '角色管理', authority: ['role_manage'] },
      },
      {
        path: '/mytest/menumanage',
        name: 'menumanage',
        component: () => import('@/views/mytest/menumanage'),
        //meta: { title: '角色管理', authority: ['role_manage'] },
      },
      {
        path: '/mytest/schoolmanage',
        name: 'schoolmanage',
        component: () => import('@/views/mytest/schoolmanage'),
        //meta: { title: '角色管理', authority: ['role_manage'] },
      },
  ]}

]

export default new Router({
  // mode: 'history', // 后端支持可开
  scrollBehavior: () => ({ y: 0 }),
  routes: constantRouterMap
})
6、页面

src下新增views目录,存放页面代码

6.1、login/index
复制代码
<template>
  <el-form label-width="500px" class="demo-ruleForm loginform" align="center">
    <el-form-item label="用户名">
      <el-input v-model="user.userName"></el-input>
    </el-form-item>

    <el-form-item label="密    码" prop="pass">
      <el-input v-model="user.password" type="password" auto-complete="off"></el-input>
    </el-form-item>

<!--    <el-form-item label="验证码" prop="pass">-->
<!--      <el-input v-model = "code" readonly="readonly"></el-input>-->
<!--      <el-input v-model="user.code" type="code" auto-complete="off"></el-input>-->

<!--    </el-form-item>-->

    <el-form-item size="large">
      <el-button type="primary" @click="login()">登录</el-button>
      <el-button>取消</el-button>
    </el-form-item>
  </el-form>
</template>


<script>
import { login,getCode } from '@/api/login'
import { getToken, setToken, removeToken } from '@/utils/auth'
//import { getDAes } from '@/utils/crypto';
export default {

  data() {
    return {
      code:'',
      user: {
        userName: '',
        password: '',
        code:''
      }

    }
  },

  created(){
    //this.getCode()
  },

  methods: {

    getCode(){
      getCode().then(res=>{
        this.code = res.data
      })
    },

    login() {
      //this.user.password = getDAes(this.user.password),
      this.$store.dispatch('Login', this.user).then(() => {

        this.$router.push({ path: '/mytest/main' })
      })
    }
  }
}
</script>


<style>
.loginform {
  float: left;
  margin: auto;
}
</style>
6.2、导航页面
(1)mytest/index菜单导航
复制代码
<template>
  <div>
    <div>
      <el-row class="tac" style="height:100%">
        <el-col :span="4">
          <el-menu
            :default-active="$route.path"
            router
            class="el-menu-vertical-demo"
          >

            <el-menu-item index="/mytest/main" >
              <i class="el-icon-menu">个人中心</i>
              <router-link to="/mytest/main"></router-link>
            </el-menu-item>


            <el-menu-item index="/mytest/usermanage" v-if="authorities.includes('user_manage')">
              <i class="el-icon-menu">用户管理</i>
              <router-link to="/mytest/usermanage"></router-link>
            </el-menu-item>
            <el-menu-item index="/mytest/rolemanage" v-if="authorities.includes('role_manage')">
              <i class="el-icon-menu">角色管理</i>
              <router-link to="/mytest/mytest2"></router-link>
            </el-menu-item>
            <el-menu-item index="/mytest/menumanage" v-if="authorities.includes('menu_manage')">
              <i class="el-icon-menu">菜单管理</i>
              <router-link to="/mytest/menumanage"></router-link>
            </el-menu-item>

            <el-menu-item index="/mytest/schoolmanage" v-if="authorities.includes('school_manage')">
              <i class="el-icon-menu">学校管理</i>
              <router-link to="/mytest/schoolmanage"></router-link>
            </el-menu-item>
<!--            &lt;!&ndash;二级子菜单&ndash;&gt;-->
<!--            <el-submenu index="1">-->
<!--              <template slot="title">-->
<!--                <i class="el-icon-location"></i>-->
<!--                <span>二级菜单</span>-->
<!--              </template>-->
<!--              <el-menu-item-group>-->
<!--                <el-menu-item index="/mytest/mytest41">-->
<!--                  <i class="el-icon-menu"></i>-->
<!--                  <router-link to="/mytest/mytest41">mytest41</router-link>-->
<!--                </el-menu-item>-->
<!--                <el-menu-item index="/mytest/mytest42">-->
<!--                  <i class="el-icon-menu"></i>-->
<!--                  <router-link to="/mytest/mytest42">mytest42</router-link>-->
<!--                </el-menu-item>-->
<!--              </el-menu-item-group>-->
<!--            </el-submenu>-->
<!--            &lt;!&ndash;三级子菜单&ndash;&gt;-->
<!--            <el-submenu index="2">-->
<!--              <template slot="title">-->
<!--                <i class="el-icon-location"></i>-->
<!--                <span>三级菜单</span>-->
<!--              </template>-->
<!--              <el-menu-item-group>-->
<!--                <el-menu-item index="/mytest/mytest51">-->
<!--                  <i class="el-icon-menu"></i>-->
<!--                  <router-link to="/mytest/mytest51">mytest51</router-link>-->
<!--                </el-menu-item>-->

<!--                &lt;!&ndash;三级&ndash;&gt;-->
<!--                <el-submenu index="3">-->
<!--                  <template slot="title">-->
<!--                    <i class="el-icon-location"></i>-->
<!--                    <span>三级子菜单</span>-->
<!--                  </template>-->
<!--                  <el-menu-item-group>-->
<!--                    <el-menu-item index="/mytest/mytest521">-->
<!--                      <i class="el-icon-menu"></i>-->
<!--                      <router-link to="/mytest/mytest521">mytest521</router-link>-->
<!--                    </el-menu-item>-->
<!--                    <el-menu-item index="/mytest/mytest522">-->
<!--                      <i class="el-icon-menu"></i>-->
<!--                      <router-link to="/mytest/mytest522">mytest522</router-link>-->
<!--                    </el-menu-item>-->
<!--                  </el-menu-item-group>-->
<!--                </el-submenu>-->
<!--              </el-menu-item-group>-->
<!--            </el-submenu>-->
          </el-menu>
        </el-col>
        <el-col span="20">
          <!--主体内容部分-->
          <div class="main">
            <router-view></router-view>
          </div>
        </el-col>
      </el-row>
    </div>
  </div>
</template>
<script>
export default {
  data(){
    return {

    }
  },
  computed: {
    authorities() {
      return this.$store.state.user.authorities
    }
  }
}
</script>
(2)mytest/main首页
复制代码
<template>
  <div>this is main</div>
</template>
(3)/mytest/usermanage
复制代码
<template>
  <div>this is user manage</div>
</template>
(4)/mytest/menumanage
复制代码
<template>
  <div>this is menu manage</div>
</template>

其他两个同上

7、权限拦截permission.js
复制代码
import router from './router'
import store from './store'
import NProgress from 'nprogress' // Progress 进度条
import 'nprogress/nprogress.css'// Progress 进度条样式
import { Message } from 'element-ui'
import { getToken } from '@/utils/auth' // 验权

const whiteList = ['/login'] // 不重定向白名单
router.beforeEach((to, from, next) => {
  NProgress.start()
  if (getToken()) {
    if (to.path === '/login') {
      next({ path: '/' })
      NProgress.done() // if current page is dashboard will not trigger	afterEach hook, so manually handle it
    } else {
      if (store.getters.authorities.length === 0) {
        // 拉取用户权限信息
        store.dispatch('GetInfo').then(res => {
          // 从后端获取的权限
          const authorities = res


          // 前端路由加载动态权限
          store.dispatch('GenerateRoutes', { authorities }).then(() => { // 生成可访问的路由表
            // alert('store.getters.addRouters' + store.getters.addRouters.length)
            router.addRoutes(store.getters.addRouters)// 添加动态路由

            next({ ...to })// hack方法 确保addRoutes已完成
          })
        }).catch((err) => {
          store.dispatch('FedLogOut').then(() => {
            Message.error(err || 'Verification failed, please login again')
            next({ path: '/' })
          })
        })
      } else {
        next()
      }
    }
  } else {
    if (whiteList.indexOf(to.path) !== -1) {
      next()
    } else {
      next('/login')
      NProgress.done()
    }
  }
})

router.afterEach(() => {
  NProgress.done() // 结束Progress
})
8、项目入口

(1)main.js

复制代码
/*

import Vue from 'vue'
import App from './App'
import router from './router'

//引入依赖
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import { VueJsonp }  from 'vue-jsonp'
//加载自定义文件
import '@/permission' // permission control


Vue.config.productionTip = false

//加载引入的依赖
Vue.use(VueJsonp)
Vue.use(ElementUI)

new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>'
})
*/
import Vue from 'vue'

//import 'normalize.css/normalize.css'// A modern alternative to CSS resets

import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import locale from 'element-ui/lib/locale/lang/zh-CN' // lang i18n

//import '@/styles/index.scss' // global css
import App from './App'
import router from './router'
import store from './store'
//import '@/icons' // icon
import '@/permission' // permission control
import VueJsonp from 'vue-jsonp'

Vue.use(VueJsonp)

Vue.use(ElementUI, { locale })

Vue.config.productionTip = false

new Vue({
  el: '#app',
  router,
  store,
  render: h => h(App)
})

(2)App.vue

复制代码
<template>
  <div id="app">
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

三、测试

1、登录

输入admin/123,f12查看控制台报错

复制代码
Access to XMLHttpRequest at 'http://localhost:2222/securityDemo/login' from origin 'http://localhost:9528' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

后端加上跨域配置即可

复制代码
package com.demo.security.config;

import com.demo.security.filter.UrlTwoFilter;
import com.demo.security.interceptor.ParamInterceptor;
import com.demo.security.interceptor.ParamOneInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Autowired
    private ParamInterceptor paramInterceptor;

    @Autowired
    private ParamOneInterceptor paramOneInterceptor;


    @Autowired
    private UrlTwoFilter twoFilter;

    //设置跨域
    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin("http://localhost:9528");
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }


    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(paramOneInterceptor);
        registry.addInterceptor(paramInterceptor).addPathPatterns("/**");
        //registry.addInterceptor(paramOneInterceptor);
    }

    @Bean
    public FilterRegistrationBean<UrlTwoFilter> getIpFilter() {
        FilterRegistrationBean<UrlTwoFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(twoFilter);
        registrationBean.setEnabled(true);
        registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);
        registrationBean.setUrlPatterns(List.of("/*"));
        return registrationBean;
    }
}

重启后端再次访问即可成功登录。

2、菜单权限
(1)admin登录:

(2)zs/123登录

(3)ls/123登录

相关推荐
崔庆才丨静觅6 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60617 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了7 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅7 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅8 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅8 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment8 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅8 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊8 小时前
jwt介绍
前端
爱敲代码的小鱼8 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax