Ruoyi框架【permission.js】【request.js】【store=>modules=>permission.js】【vite.config.js】
Ruoyi框架【permission.js】【request.js】【store=>modules=>permission.js】【vite.config.js】
·
permission.js(全局路由守卫和登录判断)详细讲解:
import router from './router'
import store from './store'
import { ElMessage } from 'element-plus'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import { getToken } from '@/utils/auth'
import { isHttp } from '@/utils/validate'
import { isRelogin } from '@/utils/request'
NProgress.configure({ showSpinner: false });
// 接口白名单,不需要登录即可访问
const whiteList = ['/login', '/auth-redirect', '/bind', '/register'];
// 路由守卫,在路由跳转前进行权限判断(三个参数分别为: 路由信息, 跳转的路由, 跳转的方法)
router.beforeEach((to, from, next) => {
// 请求路由时进度条开始
NProgress.start()
// 判断是否有token
if (getToken()) {
// 设置页面标题
to.meta.title && store.dispatch('settings/setTitle', to.meta.title)
/* has token*/
// 如果用户存在令牌的情况请求登录页面,避免重复登录,直接跳转到首页
// 判断是否为登录页面
if (to.path === '/login') {
// 直接跳转到首页,当然取决于你的路由重定向到哪里
next({ path: '/' })
// 一定要结束进度条
NProgress.done()
} else {
// 判断是否已经获取用户信息
if (store.getters.roles.length === 0) {
// length为0,则为未获取用户信息,需要重新获取
isRelogin.show = true // 重新登录弹窗
// 判断当前用户是否已拉取完user_info信息
store.dispatch('GetInfo').then(() => {
isRelogin.show = false
// 从vuex中触发GenerateRoutes方法,获取后台返回的路由信息
store.dispatch('GenerateRoutes').then(accessRoutes => {
// 根据roles权限生成可访问的路由表
accessRoutes.forEach(route => {
if (!isHttp(route.path)) {
router.addRoute(route) // 动态添加可访问路由表
}
})
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
})
}).catch(err => {
// 获取用户信息失败,清除token,跳转到登录页面重新登录
store.dispatch('LogOut').then(() => {
ElMessage.error(err)
next({ path: '/' })
})
})
} else {
next()
}
}
} else {
// 没有token
if (whiteList.indexOf(to.path) !== -1) {
// 在免登录白名单,直接进入
next()
} else {
next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页
NProgress.done()
}
}
})
router.afterEach(() => {
// 每次请求结束后,进度条结束
NProgress.done()
})
request.js:
import axios from 'axios'
import { ElNotification , ElMessageBox, ElMessage, ElLoading } from 'element-plus'
import store from '@/store'
import { getToken } from '@/utils/auth'
import errorCode from '@/utils/errorCode'
import { tansParams, blobValidate } from '@/utils/dtcloud'
import cache from '@/plugins/cache'
import { saveAs } from 'file-saver'
let downloadLoadingInstance;
// 是否显示重新登录
export let isRelogin = { show: false };
// 设置默认请求头
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
// 创建axios实例
const service = axios.create({
// axios中请求配置有baseURL选项,表示请求URL公共部分
baseURL: import.meta.env.VITE_APP_BASE_API,
// 超时
timeout: 10000
})
// request拦截器
service.interceptors.request.use(config => {
// 是否需要设置 token
const isToken = (config.headers || {}).isToken === false
// 是否需要防止数据重复提交
const isRepeatSubmit = (config.headers || {}).repeatSubmit === false
if (getToken() && !isToken) {
config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
}
// get请求映射params参数
if (config.method === 'get' && config.params) {
// 拼接参数
let url = config.url + '?' + tansParams(config.params);
url = url.slice(0, -1);// 去掉最后一个&
config.params = {};// 清空参数
config.url = url;// 重新赋值
}
if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {// 防止数据重复提交
const requestObj = {// 用于存储请求的对象
url: config.url,// 请求地址
data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,// 请求参数
time: new Date().getTime()// 请求时间
}
const sessionObj = cache.session.getJSON('sessionObj')// 获取sessionStorage中的请求对象
if (sessionObj === undefined || sessionObj === null || sessionObj === '') {// 判断sessionStorage中是否有请求对象
cache.session.setJSON('sessionObj', requestObj)// 将请求对象存入sessionStorage
} else {
const s_url = sessionObj.url; // 请求地址
const s_data = sessionObj.data; // 请求数据
const s_time = sessionObj.time; // 请求时间
const interval = 1000; // 间隔时间(ms),小于此时间视为重复提交
if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) {// 判断请求是否重复
const message = '数据正在处理,请勿重复提交';
console.warn(`[${s_url}]: ` + message)// 打印警告信息
return Promise.reject(new Error(message))// 抛出异常
} else {
cache.session.setJSON('sessionObj', requestObj)// 将请求对象存入sessionStorage
}
}
}
return config
}, error => {
console.log(error)
Promise.reject(error)
})
// 响应拦截器
service.interceptors.response.use(res => {// 响应成功关闭loading
// 未设置状态码则默认成功状态
const code = res.data.code || 200;
// 获取错误信息
const msg = errorCode[code] || res.data.msg || errorCode['default']
// 二进制数据则直接返回
if(res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer'){
return res.data
}
if (code === 401) {
if (!isRelogin.show) {
isRelogin.show = true;// 防止重复弹出
ElMessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', {
confirmButtonText: '重新登录',
cancelButtonText: '取消',
type: 'warning'
}
).then(() => {
isRelogin.show = false;
store.dispatch('LogOut').then(() => {// 为了重新实例化vue-router对象 避免bug
location.href = '/index';// 跳转到登录页
})
}).catch(() => {
isRelogin.show = false;
});
}
return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
} else if (code === 500) {
ElMessage({
message: msg,
type: 'error'
})
return Promise.reject(new Error(msg))// 抛出异常
} else if (code !== 200) {
ElNotification.error({
title: msg
})
return Promise.reject('error')
} else {
return Promise.resolve(res.data)
}
},
error => {
console.log('err' + error)
let { message } = error;
if (message == "Network Error") {// 网络错误
message = "后端接口连接异常";
}
else if (message.includes("timeout")) {// 请求超时
message = "系统接口请求超时";
}
else if (message.includes("Request failed with status code")) {// 请求失败
message = "系统接口" + message.substr(message.length - 3) + "异常";
}
ElMessage({
message: message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
}
)
// 通用下载方法
// url: 下载地址
// params: 参数
// fileName: 文件名称
export function download(url, params, filename) {
downloadLoadingInstance = ElLoading.service({ text: "正在下载数据,请稍候", background: "rgba(0, 0, 0, 0.7)", })// 打开loading
return service.post(url, params, {// 发送请求
transformRequest: [(params) => { return tansParams(params) }],// 参数转换
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },// 设置请求头
responseType: 'blob'// 设置响应类型
}).then(async (data) => {
// isLogin(data);// 判断是否登录
const isLogin = await blobValidate(data);
if (isLogin) {// 已登录
const blob = new Blob([data])// 创建blob对象
saveAs(blob, filename)// 保存文件
} else {// 未登录
const resText = await data.text();// 获取响应文本
const rspObj = JSON.parse(resText);// 转换为对象
const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default']// 获取错误信息
ElMessage.error(errMsg);// 提示错误信息
}
downloadLoadingInstance.close();// 关闭loading
}).catch((r) => {
console.error(r)// 打印错误信息
ElMessage.error('下载文件出现错误,请联系管理员!')
downloadLoadingInstance.close();// 关闭loading
})
}
export default service// 导出axios实例
store=>modules=>permission.js (后台返回路由处理拼接):
import { constantRoutes } from '@/router'
import { getRouters,getLocalRouters } from '@/api/menu'
import Layout from '@/layout/index'
import ParentView from '@/components/ParentView'
import InnerLink from '@/layout/components/InnerLink'
// 匹配views里面所有的.vue文件
const modules = import.meta.glob('./../../views/**/*.vue')
const permission = {
state: {
routes: [],// 所有路由
addRoutes: [],// 有权限的路由
defaultRoutes: [],// 默认路由
topbarRouters: [],// 顶部菜单
sidebarRouters: []// 侧边栏菜单
},
mutations: {
SET_ROUTES: (state, routes) => {// 设置路由
state.addRoutes = routes// 有权限的路由
state.routes = constantRoutes.concat(routes)// 所有路由
},
SET_DEFAULT_ROUTES: (state, routes) => {// 设置默认路由
state.defaultRoutes = constantRoutes.concat(routes)// 默认路由
},
SET_TOPBAR_ROUTES: (state, routes) => {// 设置顶部菜单
state.topbarRouters = routes// 顶部菜单
},
SET_SIDEBAR_ROUTERS: (state, routes) => {// 设置侧边栏菜单
state.sidebarRouters = routes// 侧边栏菜单
},
},
actions: {
// 生成路由
GenerateRoutes({ commit }) {
return new Promise(resolve => {// 获取后台路由
// 向后端请求路由数据
getRouters().then(res => {
const sdata = JSON.parse(JSON.stringify(res.data))// 深拷贝
const rdata = JSON.parse(JSON.stringify(res.data))// 深拷贝
const defaultData = JSON.parse(JSON.stringify(res.data))// 深拷贝
const sidebarRoutes = filterAsyncRouter(sdata)// 过滤路由
const rewriteRoutes = filterAsyncRouter(rdata, false, true)// 过滤路由
const defaultRoutes = filterAsyncRouter(defaultData)// 过滤路由
commit('SET_ROUTES', rewriteRoutes)// 设置路由
commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(sidebarRoutes))// 设置侧边栏菜单
commit('SET_DEFAULT_ROUTES', sidebarRoutes)// 设置默认路由
commit('SET_TOPBAR_ROUTES', defaultRoutes)// 设置顶部菜单
resolve(rewriteRoutes)// 返回有权限的路由
})
})
}
}
}
// 遍历后台传来的路由字符串,转换为组件对象
// 递归过滤异步路由表,返回符合用户角色权限的路由表
// 三个参数分别为:后台返回的路由数据,是否是侧边栏菜单,是否是顶部菜单
function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {// type为true时,不过滤component
return asyncRouterMap.filter(route => {// 遍历后台传来的路由字符串,转换为组件对象
if (type && route.children) {// 顶部菜单
route.children = filterChildren(route.children)// 过滤子路由
}
if (route.component) {// 如果路由设置了组件
// Layout ParentView 组件特殊处理
if (route.component === 'Layout') {// Layout组件特殊处理
route.component = Layout// Layout组件
} else if (route.component === 'ParentView') {// ParentView组件特殊处理
route.component = ParentView// ParentView组件
} else if (route.component === 'InnerLink') {// InnerLink组件特殊处理
route.component = InnerLink// InnerLink组件
} else {
route.component = loadView(route.component)// 路由组件
}
}
if (route.children != null && route.children && route.children.length) {// 如果有子路由,递归
route.children = filterAsyncRouter(route.children, route, type)// 递归过滤异步路由表,返回符合用户角色权限的路由表
} else {
delete route['children']// 删除子路由
delete route['redirect']// 删除重定向
}
return true
})
}
// 过滤子路由
// 三个参数分别为:后台返回的路由数据,是否是侧边栏菜单,是否是顶部菜单
function filterChildren(childrenMap, lastRouter = false) {
var children = []// 子路由
childrenMap.forEach((el, index) => {// 遍历后台传来的路由字符串,转换为组件对象
if (el.children && el.children.length) {// 如果有子路由,递归
if (el.component === 'ParentView' && !lastRouter) {// ParentView组件特殊处理
el.children.forEach(c => {// 遍历子路由
c.path = el.path + '/' + c.path// 拼接路径
if (c.children && c.children.length) {// 如果有子路由,递归
children = children.concat(filterChildren(c.children, c))// 递归过滤异步路由表,返回符合用户角色权限的路由表
return
}
children.push(c)// 子路由
})
return
}
}
if (lastRouter) {// 如果有子路由,递归
el.path = lastRouter.path + '/' + el.path// 拼接路径
}
children = children.concat(el)// 子路由
})
return children// 子路由
}
export const loadView = (view) => {// 路由懒加载
let res;
for (const path in modules) {// 遍历路由
const dir = path.split('views/')[1].split('.vue')[0];// 路由路径
if (dir === view) {// 如果路由路径和传入的路由路径相同
res = () => modules[path]();// 路由懒加载
}
}
return res;
}
export default permission
vite.config.js :
import { defineConfig, loadEnv } from 'vite'
import path from 'path'
import createVitePlugins from './vite/plugins'
// https://vitejs.dev/config/
//参数:mode:当前运行的模式,可选值为:development、production、test
//参数:command:当前运行的命令,可选值为:serve、build
export default defineConfig(({ mode, command }) => {
const env = loadEnv(mode, process.cwd())// 加载环境变量
const { VITE_APP_ENV } = env// 获取环境变量
return {
// 部署生产环境和开发环境下的URL。
// 默认情况下,vite 会假设你的应用是被部署在一个域名的根路径上
base: VITE_APP_ENV === 'production' ? '/' : '/',// 根据不同的环境设置不同的base
// 指定构建输出目录
plugins: createVitePlugins(env, command === 'build'),
resolve: {
// https://cn.vitejs.dev/config/#resolve-alias
alias: {// 设置路径别名
// 设置路径
'~': path.resolve(__dirname, './'),
// 设置别名
'@': path.resolve(__dirname, './src')
},
// https://cn.vitejs.dev/config/#resolve-extensions
// 设置导入文件的扩展名
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue']
},
// vite 相关配置
server: {// 服务配置
port: 80,// 端口
host: true,// 是否自动打开浏览器
open: true,// 是否自动打开浏览器
proxy: {// 代理
// https://cn.vitejs.dev/config/#server-proxy
'/licenseUrl': {// 代理路径
target: 'http://192.168.5.252:9110',// 代理目标地址
changeOrigin: true,// 是否跨域
rewrite: (p) => p.replace(/^\/licenseUrl/, '')// 重写路径
}
}
},
//fix:error:stdin>:7356:1: warning: "@charset" must be the first rule in the file
css: {// css 配置
postcss: {// postcss 配置
plugins: [// 插件
{
postcssPlugin: 'internal:charset-removal',// 插件名称
AtRule: {// at 规则
charset: (atRule) => {// 字符集
if (atRule.name === 'charset') {// 如果是字符集
atRule.remove();// 移除
}
}
}
}
]
}
}
}
})
更多推荐
已为社区贡献1条内容
所有评论(0)