vue-element-plus-admin整合后端实战——实现系统登录、缓存用户数据、实现动态路由
目标
整合vue-element-plus-admin前端框架,作为开发平台的前端。
准备工作
前端选用vue-element-plus-admin,地址 https://gitee.com/kailong110120130/vue-element-plus-admin。
首先clone项目,然后整合到开发平台中去。这是一个独立的前端的项目,而我将其放到后端项目根目录下,即建一个huayuan-web的目录,将vue-element-plus-admin目录下的内容放进去,相当于将前端项目视为整个工程项目的一个模块。
为什么要这么做呢?原因也简单,从架构上而言,前后端是分离的,不过当前这个平台前后端都是我在做,因此开发模式并不是前后端分别开发,通过mock数据和联调再整合到一块去,而是对于一个功能,例如组织机构管理,往往是后端和前端是一块做的。这样从开发上,从Git单次提交上,都是对于一个功能的完整处理。
既然是将前端项目视为整个工程的一个模块,是一个git仓库统一管理,那么前端项目下就不应该还存在.git目录了。如果直接删除,运行pnpm install会报错,原因是使用了husky,而husky是依赖git 才能安装。
经过几次尝试,做了以下处理。先clone,然后执行pnpm install,确保前端项目能运转起来。然后执行 pnpm unistall husky,既卸载掉husky,然后再删除掉前端项目根目录下的.git目录,这样既保证了前端项目能正常运转,又将其纳入了整个工程。
调用后端服务
完成了基本的源码下载和整合到项目工程,接下来考虑的就是怎么实现前端调用后端服务。
前端使用默认的localhost:4000,后端服务的地址是localhost:8080,首先解决前后端联通性问题。
首先调整的是vite.config.ts中的server节点下的proxy设置,具体如下:
server: {port: 4000,proxy: {// 系统管理模块'/system': {target: env.VITE_BASE_URL,changeOrigin: true } }
}
即把路径以/system起始的请求转发到后端,其中env.VITE_BASE_URL是在local.env中定义:
# 环境
NODE_ENV=development# 请求路径
VITE_BASE_URL='http://localhost:8080'# 接口前缀
VITE_API_BASEPATH=/my-api# 打包路径
VITE_BASE_PATH=/# 标题
VITE_APP_TITLE=ElementAdmin
系统登录
前后端联通后,首先实现的功能,肯定是登录。
结果看了下官方文档,只有安装、目录结构和功能组件的大概介绍,并没有如何跟后端整合的介绍。百度搜了下,结果都是基vue-element-admin的,也就是vue2.0+Element UI 的框架。看来新技术与框架只能自己来开荒了,通过源码阅读与摸索来实现。
前端框架能独立运行,输入账号密码后完成登录,进入系统首页,实际上使用的是mock数据,登录方法位于mock/user/index.ts中。
import { config } from '@/config/axios/config'
import { MockMethod } from 'vite-plugin-mock'const { result_code } = configconst timeout = 1000const List: {username: stringpassword: stringrole: stringroleId: stringpermissions: string | string[]
}[] = [{username: 'admin',password: 'admin',role: 'admin',roleId: '1',permissions: ['*.*.*']},{username: 'test',password: 'test',role: 'test',roleId: '2',permissions: ['example:dialog:create', 'example:dialog:delete']}]export default [// 列表接口{url: '/user/list',method: 'get',response: ({ query }) => {const { username, pageIndex, pageSize } = queryconst mockList = List.filter((item) => {if (username && item.username.indexOf(username) < 0) return falsereturn true})const pageList = mockList.filter((_, index) => index < pageSize * pageIndex && index >= pageSize * (pageIndex - 1))return {code: result_code,data: {total: mockList.length,list: pageList}}}},// 登录接口{url: '/user/login',method: 'post',timeout,response: ({ body }) => {const data = bodylet hasUser = falsefor (const user of List) {if (user.username === data.username && user.password === data.password) {hasUser = truereturn {code: result_code,data: user}}}if (!hasUser) {return {code: '500',message: '账号或密码错误'}}}},// 退出接口{url: '/user/loginOut',method: 'get',timeout,response: () => {return {code: result_code,data: null}}}
] as MockMethod[]
可以看到,逻辑比较简单,无非是比对下预先设置的账号密码,如一致则直接构造一个admin用户返回。
接下来,我来改造下,直接调用后端服务。
系统后端使用SpringSecurity框架,配置的登录路径是/system/user/login。
修改api/login/index.ts中的loginApi即可
export const loginApi = (data: UserType) => {return request.post({url: '/system/user/login?username=' + data.username + '&password=' + data.password,data})
}
上面把账号密码通过url参数的方式传入后端,实际是SpringSecurity的限制。SpringSecurity内置的过滤器,不从post请求的body里取数据,所以这地方做了点小处理。
完成上述调整后,使用浏览器调试功能,可以看到真正向后端发起请求了,并且后端返回了登录成功后的数据。
缓存用户数据
vue-element-plus-admin框架对用户信息做了定义,与我的设计差异较大,这地方也做了比较大的改造。
用户信息如下:
import { store } from '../index'
import { defineStore } from 'pinia'
import { useCache } from '@/hooks/web/useCache'
import { USER_KEY } from '@/constant/common'
const { wsCache } = useCache()interface UserState {account: stringname: stringforceChangePassword: stringid: stringtoken: stringbuttonPermission: string[]menuPermission: string[]
}export const useUserStore = defineStore('user', {state: (): UserState => ({account: '',name: '',forceChangePassword: '',id: '',token: '',buttonPermission: [],menuPermission: []}),getters: {getAccount(): string {return this.account}},actions: {async setUserAction(user) {this.account = user.accountthis.name = user.namethis.forceChangePassword = user.forceChangePasswordthis.id = user.idthis.token = user.tokenthis.buttonPermission = user.buttonPermissionthis.menuPermission = user.menuPermissionwsCache.set(USER_KEY, user)},async clear() {wsCache.clear()this.resetState()},resetState() {this.account = ''this.name = ''this.forceChangePassword = ''this.id = ''this.token = ''this.buttonPermission = []this.menuPermission = []}}
})export const useUserStoreWithOut = () => {return useUserStore(store)
}
包括标识、账号、姓名、是否强制修改密码、令牌、菜单权限数组和按钮权限数组这几个关键字段。
在用户登录成功后,将后端返回的用户信息缓存到浏览器SessionStorage中。
// 登录
const signIn = async () => {const formRef = unref(elFormRef)await formRef?.validate(async (isValid) => {if (isValid) {loading.value = trueconst { getFormData } = methodsconst formData = await getFormData<UserType>()try {const res = await loginApi(formData)if (res) {// 保存用户信息userStore.setUserAction(res.data)// 是否使用动态路由略}} finally {loading.value = false}}})
实现动态路由
接下来就是最复杂的一块功能改造了,即实现动态路由,根据后端返回的菜单权限,动态构造出前端路由来。
在vue-elment-ui框架里,这块功能实际是没有的,当初我自己费了不少劲最终实现了。
在vue-element-plus-admin框架中里,这块功能有了支持,预留了三种模式:
1.静态路由:也就是默认的前端独立运行模式看到的效果,所有菜单固化,预先配置好。
2.前端控制:只初始化通用的路由至路由表中。对于动态路由,在前端固定写死对应的角色。用户登录后,通过角色去遍历动态路由表,获取该角色可以访问的路由表,生成动态路由表,再通过 router.addRoutes 添加到路由实例。
3.后端控制:通过接口动态生成路由表,且遵循一定的数据结构返回。前端根据需要处理该数据为可识别的结构,再通过 router.addRoutes 添加到路由实例。
上面三种模式,第一种明显不可用,第二种勉强可用,但缺点也很明显,灵活性不够,如果服务端改动角色,前端也需要跟着改动,并且排序什么的都需要前端控制。第三种才是我们真正想要的,后端调整权限,前端无需修改,自动动态获取,处理后形成系统菜单。
虽然前端框架预留了口子,但是调整起来仍然比较复杂,下面具体说说。
首先得改一个全局变量,将store/modules/app.ts 中的dynamicRouter 设置为 true,即启用动态路由,框架在多处会首先判断该配置的取值,进行不同的处理。
其次,是修改store/modules/permission.ts 中的generateRoutes方法。
generateRoutes(type: 'admin' | 'test' | 'none',routers?: AppCustomRouteRecordRaw[] | string[]): Promise<unknown> {return new Promise<void>((resolve) => {// TODO:前后端动态路由临时添加固定路由,待去除let routerMap: AppRouteRecordRaw[] = asyncRouterMapif (type === 'admin') {// 后端过滤菜单routerMap = generateRoutesFn2(routers as AppCustomRouteRecordRaw[]).concat(routerMap)} else if (type === 'test') {// 模拟前端过滤菜单routerMap = generateRoutesFn1(cloneDeep(asyncRouterMap), routers as string[])} else {// 直接读取静态路由表routerMap = cloneDeep(asyncRouterMap)}// 动态路由,404一定要放到最后面this.addRouters = routerMap.concat([{path: '/:path(.*)*',redirect: '/404',name: '404Page',meta: {hidden: true,breadcrumb: false}}])// 渲染菜单的所有路由this.routers = cloneDeep(constantRouterMap).concat(routerMap)resolve()})},
这个方法有两个参数,第一个是指定模式,admin代表模式三,从后端接口拿到动态路由数据,第二个参数就是后端返回的路由数据。
再次,是将后端返回的路由数据,进行转换处理,成为前端需要的数据结构,需要调整/utils/routerHelper.ts中的
generateRoutesFn2方法。
// 后端控制路由生成
export const generateRoutesFn2 = (routes: AppCustomRouteRecordRaw[]): AppRouteRecordRaw[] => {const res: AppRouteRecordRaw[] = []for (const route of routes) {const data: AppRouteRecordRaw = {path: route.path,name: route.name,redirect: route.redirect,meta: route.meta}if (route.component) {const comModule =modules[`../modules/${route.component}.vue`] || modules[`../modules/${route.component}.tsx`]const component = route.component as stringif (!comModule && !component.includes('#')) {console.error(`未找到${route.component}.vue文件或${route.component}.tsx文件,请创建`)} else {// 动态加载路由文件data.component =component === '#' ? Layout : component.includes('##') ? getParentLayout() : comModule}}// recursive child routesif (route.children) {data.children = generateRoutesFn2(route.children)}res.push(data as AppRouteRecordRaw)}return res
}
数据处理和转换,跟后端返回的数据结构有关系,特别是动态引入组件部分,需根据自己的情况进行适配调整。
完成上述操作后,动态路由就实现了,回到登录环节,实现加载动态路由,然后进入系统,默认加载第一个能找到的路由。
// 登录
const signIn = async () => {const formRef = unref(elFormRef)await formRef?.validate(async (isValid) => {if (isValid) {loading.value = trueconst { getFormData } = methodsconst formData = await getFormData<UserType>()try {const res = await loginApi(formData)if (res) {// 保存用户信息userStore.setUserAction(res.data)// 是否使用动态路由if (appStore.getDynamicRouter) {const routers = res.data.menuPermission || []await permissionStore.generateRoutes('admin', routers).catch(() => {})permissionStore.getAddRouters.forEach((route) => {addRoute(route as RouteRecordRaw) // 动态添加可访问路由表})permissionStore.setIsAddRouters(true)push({ path: redirect.value || permissionStore.addRouters[0].path })} else {await permissionStore.generateRoutes('none').catch(() => {})permissionStore.getAddRouters.forEach((route) => {addRoute(route as RouteRecordRaw) // 动态添加可访问路由表})permissionStore.setIsAddRouters(true)push({ path: redirect.value || permissionStore.addRouters[0].path })}}} finally {loading.value = false}}})
}
总结
今天主要介绍了如何对vue-element-plus-admin改造,实现系统登录、缓存用户数据以及动态路由。完成上述操作后,基本实现了前后端的打通工作。
平台设计与设计专栏地址:https://blog.csdn.net/seawaving/category_12230971.html
开源项目地址:[https://gitee.com/popsoft/huayuan-development-platform](https://gitee.com/popsoft/huayuan-development-platform]()
欢迎收藏、点赞、评论。
相关文章:
vue-element-plus-admin整合后端实战——实现系统登录、缓存用户数据、实现动态路由
目标 整合vue-element-plus-admin前端框架,作为开发平台的前端。 准备工作 前端选用vue-element-plus-admin,地址 https://gitee.com/kailong110120130/vue-element-plus-admin。 首先clone项目,然后整合到开发平台中去。这是一个独立的前…...

Shader Graph2-PBR介绍之表面属性(图解)
PBR的实现由光线和表面属性决定,下面我们介绍一下表面属性。这个5个属性在ShaderGraph的根节点是经常的看到,左侧是Unity中的,右侧是UE中的。 在没有Metallic金属的情况下,基础颜色值就决定了颜色的漫反射值,也就是说基…...

Java多线程编程,Thread类的基本用法讲解
文章目录如何创建一个线程start 与 run线程休眠线程中断线程等待获取线程实例如何创建一个线程 之前我们介绍了什么是进程与线程,那么我们如何使用代码去创建一个线程呢?线程操作是操作系统中的概念,操作系统内核实现了线程这样的机制&#…...

TIA博途Wincc_多路复用变量的使用方法示例(实现多台相同设备参数的画面精简)
TIA博途Wincc_多路复用变量的使用方法示例(实现多台相同设备参数的画面精简) 使用多路复用变量的好处: 当项目中存在多个相同的设备(例如:变频器、电机等),对这些设备在HMI上进行监控或修改参数时,不再需要逐个建立画面或IO域等,只需通过单个画面或IO域组合即可实现对…...

关于console你不知道的那些事
看到标题,大家会不会想,我都在前端岗位叱咤风云这么多年了, console 这个玩意用你讲 但是, 今天我将带你看到不一样的 console, 可以带来更多的帮助 了解 console 什么是 console ? console 其实是 JavaScript 内的一个原生对象。内部存储的方法大部…...

Java设计模式-责任链模式
1 概述 在现实生活中,常常会出现这样的事例:一个请求有多个对象可以处理,但每个对象的处理条件或权限不同。例如,公司员工请假,可批假的领导有部门负责人、副总经理、总经理等,但每个领导能批准的天数不同…...

顺序表设计循环队列
使用顺序表来设计队列的最大优势是顺序表有可以定位元素的下标。 并且可以以Mod来使数组下标循环 #include<stdio.h> #include<stdlib.h> #include<assert.h> #include<stdbool.h> typedef int CQDataType; typedef struct { int* array; in…...
UEFI 基础教程 (十四) - 设置默认启动项为UEFI Shell
一 编写源代码 OvmfPkg/Library/PlatformBootManagerLib/BdsPlatform.c UINTN BootOptionPriority ( CONST EFI_BOOT_MANAGER_LOAD_OPTION *BootOption ) { DEBUG ((EFI_D_ERROR," [CSDN] BootOptionPriority %S .\n", BootOption->Description)); if (StrCmp (…...

python编程:判断一个数是否是超级素数
请定义一个函数,实现判断一个数是否是超级素数,并输出判断的结果。 一、编程题目 我们都知道,素数是除了1之外只能被自身整数的数,1除外。如果一个素数,去除一位、两位或多位后依然是素数,则我们称该素数为超级素数。…...

雷迪RD8200管线探测仪参数/管线仪使用方法/管线仪说明书
预防损坏和工作效率是我们客户面临的最大挑战 全新的 RD8200可以解决这些问题。这是我们功能优秀的精密管线仪系列,设计时充分考虑了操作员的需要。 预防损坏的专业选择 速度、准确性和可靠的性能 易于设置和使用 阳光下可读的显示屏、高性能的音频系统和用于嘈杂…...
会话共享保存到redis
1. 安装redis服务 [rootdb01 ~]# yum -y install redis 2. 配置redis服务 修改配置文件可以让其他服务器远程连接 127.0.0.1:6379 # 默认只能本地连接 [rootdb01 ~]# vim /etc/redis.conf [rootdb01 ~]# grep 172.16.1.51 /etc/redis.conf bind 127.…...

python 曲线平滑处理——方法总结(Savitzky-Golay 滤波器、make_interp_spline插值法和convolve滑动平均滤波)
文章目录1 插值法对曲线平滑处理1.1 插值法的常见实现方法1.2 拟合和插值的区别1.3 代码实例2 Savitzky-Golay 滤波器实现曲线平滑2.1 问题描述2.2 Savitzky-Golay 滤波器--调用讲解2.3 Savitzky-Golay 曲线平滑处理 示例2.4 Savitzky-Golay原理剖析3 基于Numpy.convolve实现滑…...
小驰私房菜_10_camx Otp Dump
#小驰私房菜# #camx# #Otp Dump# 本篇文章分下面几点展开: 1、otp dump的目的? 2、如何打开otp dump开关? 3、otp guide手册如何查看? 4、如何初步确认dump 出来的数据是否正确? 一、otp dump的目的 关于otp的一些概念,这里就不做过多的介绍了,不了解的同学,可以先去…...

priority_queue(堆)干货归纳+用法示例
10.priority_queue一.priority_queue(堆Heap)简介1.堆的特点:2.使用场景:二.成员函数1.构造函数:priority_queue构造函数方式:2.push()函数:向priority_queue中插入一个元素:3.pop()…...

miniprogram-to-uniapp使用指南(各种小程序项目转换为uni-app项目)
小程序分类:uni-app qq小程序 支付宝小程序 百度小程序 钉钉小程序 微信小程序 小程序转成uni_app 小程序转为uni_app 小程序转uni_app 小程序转换 工具现在支持npm全局库、HBuilderX插件两种方式使用,任君选择,HBuilderX插件地址:…...

BZOJ2720: [Violet 5]列队春游 【概率与期望】
题意自行理解,先讲一下概率和期望怎么算 概率 概率准确的定义自行百度,这里就不赘述了 概率的计算其实很简单,就是将符合条件的情况除以总共的情况 下面以掷骰子为例: 问题:将一个骰子掷出,666朝上的概率是多少 …...

脉诊之脉象——平脉,常见病脉,七绝脉
平脉与病脉诊脉纲领平人脉象常见病脉浮脉沉脉迟脉数脉虚脉实脉涩脉洪脉细脉滑脉弦脉紧脉长脉短脉弱脉芤脉结脉代脉七绝脉釜沸脉鱼翔脉虾游脉屋漏脉雀啄脉解索脉弹石脉预后诊脉纲领 脉跳动的力度:有力者,气足也。无力者,气不足也。 脉…...

第05章_存储引擎
第05章_存储引擎 🏠个人主页:shark-Gao 🧑个人简介:大家好,我是shark-Gao,一个想要与大家共同进步的男人😉😉 🎉目前状况:23届毕业生,目前在某…...

【新2023Q2押题JAVA】华为OD机试 - 挑选字符串
最近更新的博客 华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为od机试,独家整理 已参加机试人员的实战技巧本篇题解:挑选字符串 题目 给定a-z,…...
职场「OKR」,魔幻又内卷
个人习惯称之为【O-KR-KPI】组合; 01从进厂实习那天开始,就接触了KPI的概念; 互联网公司,年初入职,可能因为那天是周五,又赶上月底,少不了要把KPI搬出来折腾一番; 天时,…...
零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?
一、核心优势:专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发,是一款收费低廉但功能全面的Windows NAS工具,主打“无学习成本部署” 。与其他NAS软件相比,其优势在于: 无需硬件改造:将任意W…...
逻辑回归:给不确定性划界的分类大师
想象你是一名医生。面对患者的检查报告(肿瘤大小、血液指标),你需要做出一个**决定性判断**:恶性还是良性?这种“非黑即白”的抉择,正是**逻辑回归(Logistic Regression)** 的战场&a…...
day52 ResNet18 CBAM
在深度学习的旅程中,我们不断探索如何提升模型的性能。今天,我将分享我在 ResNet18 模型中插入 CBAM(Convolutional Block Attention Module)模块,并采用分阶段微调策略的实践过程。通过这个过程,我不仅提升…...

智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql
智慧工地管理云平台系统,智慧工地全套源码,java版智慧工地源码,支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求,提供“平台网络终端”的整体解决方案,提供劳务管理、视频管理、智能监测、绿色施工、安全管…...
土地利用/土地覆盖遥感解译与基于CLUE模型未来变化情景预测;从基础到高级,涵盖ArcGIS数据处理、ENVI遥感解译与CLUE模型情景模拟等
🔍 土地利用/土地覆盖数据是生态、环境和气象等诸多领域模型的关键输入参数。通过遥感影像解译技术,可以精准获取历史或当前任何一个区域的土地利用/土地覆盖情况。这些数据不仅能够用于评估区域生态环境的变化趋势,还能有效评价重大生态工程…...

智能分布式爬虫的数据处理流水线优化:基于深度强化学习的数据质量控制
在数字化浪潮席卷全球的今天,数据已成为企业和研究机构的核心资产。智能分布式爬虫作为高效的数据采集工具,在大规模数据获取中发挥着关键作用。然而,传统的数据处理流水线在面对复杂多变的网络环境和海量异构数据时,常出现数据质…...
今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存
文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...
Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信
文章目录 Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信前言一、网络通信基础概念二、服务端与客户端的完整流程图解三、每一步的详细讲解和代码示例1. 创建Socket(服务端和客户端都要)2. 绑定本地地址和端口&#x…...
Java编程之桥接模式
定义 桥接模式(Bridge Pattern)属于结构型设计模式,它的核心意图是将抽象部分与实现部分分离,使它们可以独立地变化。这种模式通过组合关系来替代继承关系,从而降低了抽象和实现这两个可变维度之间的耦合度。 用例子…...

GruntJS-前端自动化任务运行器从入门到实战
Grunt 完全指南:从入门到实战 一、Grunt 是什么? Grunt是一个基于 Node.js 的前端自动化任务运行器,主要用于自动化执行项目开发中重复性高的任务,例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...