接上一篇SpringBoot+dubbo+Spring Sercurity+VUE实例----(后端),这一篇总结下前端。
一、config配置:
1、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:9999/demo/"',
})
2、test.env.js测试环境:
'use strict'
module.exports = {
NODE_ENV: '"test"',
// BASE_API: '"http://127.0.0.1:8179"'
BASE_API: '"http://test.xxx.com/mydemo/"',
RELOAD_URL: '"http://test.xxx.com/mydemo/"'
// BASE_API: '"https://easy-mock.com/mock/5950a2419adc231f356a6636/vue-admin"',
}
3、build.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
}
}
二、src:
1、main.js:
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、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
})
3、utils:
(1)、auth.js:
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 请求util:
import axios from 'axios'
// 配置session跨域
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
4、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
})
}
//获取我的权限列表
export function getMyAuthorities() {
return request({
url: '/user/getMyAuthorities',
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'
})
}
5、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)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)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)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
var permissions = []
data.forEach(item=>{
permissions.push(item.authorityCode);
})
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
6、router 路由:
import Vue from 'vue'
import Router from 'vue-router'
// in development-env not use lazy-loading, because lazy-loading too many pages will cause webpack hot update too slow. so only in production use lazy-loading;
// detail: https://panjiachen.github.io/vue-element-admin-site/#/lazy-loading
Vue.use(Router)
/* Layout */
import Layout from '../views/layout/Layout'
export const constantRouterMap = [
{ path: '/404', component: () => import('@/views/404'), hidden: true },
{ path: '/login', component: () => import('@/views/login/index'), hidden: true },
{
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 (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: '/login', component: () => import('@/views/login/index'), hidden: true },
{
path: '/roleManage',
redirct: '/roleManage/index',
name: 'roleManage',
component: Layout,
meta: { title: '角色管理', authority: ['role_manage'] },
noDropdown: true,
children: [
{
path: 'index',
name: 'index',
component: () => import('@/views/roleManage/index'),
meta: { title: '首页', authority: ['role_manage'], keepAlive: false }
},
{
path: 'detail',
name: 'detail',
// hidden: true,
component: () => import('@/views/roleManage/detail'),
meta: { title: '详情', authority: ['role_manage'], keepAlive: false }
}
]
},
{
path: '/userManage',
redirct: '/userManage/index',
name: 'userManage',
component: Layout,
meta: { title: '用户管理', authority: ['user_manage'] },
noDropdown: true,
children: [
{
path: 'index',
name: 'index',
component: () => import('@/views/userManage/index'),
meta: { title: '用户管理', authority: ['user_manage'], keepAlive: false }
}
]
}
]
export default new Router({
// mode: 'history', // 后端支持可开
scrollBehavior: () => ({ y: 0 }),
routes: constantRouterMap
})
7、views页面:
(1)登录:
登录
取消
(2)用户管理页面:
(3)角色管理页面:
我是详情
我是首页