跨境派

跨境派

跨境派,专注跨境行业新闻资讯、跨境电商知识分享!

当前位置:首页 > 卖家故事 > vue-element-plus-admin整合后端实战——实现系统登录、缓存用户数据、实现动态路由

vue-element-plus-admin整合后端实战——实现系统登录、缓存用户数据、实现动态路由

时间:2024-04-05 07:05:35 来源:网络cs 作者:言安琪 栏目:卖家故事 阅读:

标签: 实现  用户  数据  动态  系统  实战 
阅读本书更多章节>>>>

目标

整合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: string  password: string  role: string  roleId: string  permissions: 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 } = query      const mockList = List.filter((item) => {        if (username && item.username.indexOf(username) < 0) return false        return 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 = body      let hasUser = false      for (const user of List) {        if (user.username === data.username && user.password === data.password) {          hasUser = true          return {            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: string  name: string  forceChangePassword: string  id: string  token: string  buttonPermission: 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.account      this.name = user.name      this.forceChangePassword = user.forceChangePassword      this.id = user.id      this.token = user.token      this.buttonPermission = user.buttonPermission      this.menuPermission = user.menuPermission      wsCache.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 = true      const { getFormData } = methods      const 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[] = asyncRouterMap        if (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 string      if (!comModule && !component.includes('#')) {        console.error(`未找到${route.component}.vue文件或${route.component}.tsx文件,请创建`)      } else {        // 动态加载路由文件        data.component =          component === '#' ? Layout : component.includes('##') ? getParentLayout() : comModule      }    }    // recursive child routes    if (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 = true      const { getFormData } = methods      const 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改造,实现系统登录、缓存用户数据以及动态路由。完成上述操作后,基本实现了前后端的打通工作。

开发平台资料

平台名称:一二三开发平台
简介: 企业级通用开发平台
设计资料:csdn专栏
开源地址:Gitee
开源协议:MIT

阅读本书更多章节>>>>

本文链接:https://www.kjpai.cn/gushi/2024-04-05/154206.html,文章来源:网络cs,作者:言安琪,版权归作者所有,如需转载请注明来源和作者,否则将追究法律责任!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。

文章评论