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搬出来折腾一番; 天时,…...
基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真
目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销,平衡网络负载,延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...
C++:std::is_convertible
C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...
模型参数、模型存储精度、参数与显存
模型参数量衡量单位 M:百万(Million) B:十亿(Billion) 1 B 1000 M 1B 1000M 1B1000M 参数存储精度 模型参数是固定的,但是一个参数所表示多少字节不一定,需要看这个参数以什么…...
相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了: 这一篇我们开始讲: 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下: 一、场景操作步骤 操作步…...
解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八
现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet,点击确认后如下提示 最终上报fail 解决方法 内核升级导致,需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...
对WWDC 2025 Keynote 内容的预测
借助我们以往对苹果公司发展路径的深入研究经验,以及大语言模型的分析能力,我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际,我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测,聊作存档。等到明…...
postgresql|数据库|只读用户的创建和删除(备忘)
CREATE USER read_only WITH PASSWORD 密码 -- 连接到xxx数据库 \c xxx -- 授予对xxx数据库的只读权限 GRANT CONNECT ON DATABASE xxx TO read_only; GRANT USAGE ON SCHEMA public TO read_only; GRANT SELECT ON ALL TABLES IN SCHEMA public TO read_only; GRANT EXECUTE O…...
oracle与MySQL数据库之间数据同步的技术要点
Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异,它们的数据同步要求既要保持数据的准确性和一致性,又要处理好性能问题。以下是一些主要的技术要点: 数据结构差异 数据类型差异ÿ…...
WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成
厌倦手动写WordPress文章?AI自动生成,效率提升10倍! 支持多语言、自动配图、定时发布,让内容创作更轻松! AI内容生成 → 不想每天写文章?AI一键生成高质量内容!多语言支持 → 跨境电商必备&am…...
IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)
文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...
