Vue-根据角色获取菜单动态添加路由

文章目录

    • 前提提要
    • 需求分析
    • 具体实现
      • 配置静态路由
      • 路由权限判断
      • 登录
      • 添加动态路由
      • 修复刷新路由丢失问题
    • 结语

如果大家写过后台管理系统的项目,那么动态路由一定是绕不开的,如果想偷懒的话,就把所有路由一开始都配置好,然后只根据后端返回的菜单列表渲染就好菜单就好了,但是这样的隐患就是我在地址栏输入的地址的时候,也会进入这个页面,不偷懒的方法就是本文要介绍的,真动态路由了,当然不会仅仅只是介绍使用数据怎么换成动态路由添加就好了,会从登录获取token后请求菜单列表…最后注册完成,这一系列流程完整的实现一次,相信对于第一次接触这个案例的朋友会有帮助

前提提要

  1. 本文有些东西我不会详细的说,比如后端部分,前端代理啊,基于 element-ui 的递归菜单封装等其他组件使用等等,我不会在做额外的赘述了,后端这个流程包裹这些封装,后面我会单独开一篇文章来说明
  2. 前端 vue 项目结构部分也不会太过详细的说明,所以观看本文还是需要一定的基础,至少知道vue的基础语法、用过 vue-router 和 vuex 吧,要求还是不高的

需求分析

  1. 在实现我们这个需求,不难想到主要就是完成登录,通过登录获取到正确的菜单列表,通过菜单列表进行渲染

  2. 但是完成这个步骤的话,我们还需要捋一下页面的关系,按照我们的开发时态来说,我们启动一个项目之后,会通过 http://localhost:8080/ 这样的一个地址在浏览中打开

  3. 打开这个地址之后,触发的是什么路径,是不是 /,表示根路径,在后台管理系统中,一般这个跟路径我们会映射到什么组件上,是不是 layout 组件,比如这样的,如图:

    在这里插入图片描述

  4. 但是这样的话就和我们的需求有点不一样了,我们要先登录啊,都没登录怎么能打开这个呢?所以一把来说,我们一般会要么把 ‘/’ 的路径触发时,重定向到 ‘/login’,或者在全局路由前置守卫中,通过登录的状态来决定是不是跳转到登录页,一般我们使用第二种,因为后台管理系统中,一定会有路由权限的判断,到时候一样会来改动这个,所以选择后者,至于实现部分,我们后面再看

  5. 完成了上述的操作之后,就是登录了,登录之后获取菜单列表数据,拿到之后我们就直接注册吗?

  6. 我们知道,这种菜单,往往会有一级、二级、三级等等不同级别的菜单,而是不是每个菜单都需要注册的呢,其实不然,我们需要注册的仅仅是需要展示的那一部分菜单,比如在我们的案例中,设备管理是一个一级菜单,但是存在子级菜单,那么此时这个设备管理菜单就是不需要注册的,如图:

    在这里插入图片描述

  7. 所以这一点我们也需要做一下区分,但是具体注册那些呢?这些就还是要在前端先配置好,但是这个配置不会是直接配置到 route 中,是一个映射关系,比如定义了 a = 组件A,然后依次书写,把所有会展示的页面通过这样的方式,用一个文件存储起来,那么通过后端返回的菜单列表数据时,就可以进行一个对比,筛选,取出符合条件的数据,组装成一个适配业务的 route 进行注册

  8. 而通过这样的匹配,我们最后是可以得到一个数组的,[route1, route2, …],得到这个数组之后,使用 vueRouter的 addRoute 方法添加即可

  9. 这里需要注意的事情是,我所演示的案例中,所有的子组件都是在 main 区域显示的,所以我就不需要在去单独的关心这些子组件的层级关系了,但是如果某个项目中的,层级关系如图:

    在这里插入图片描述

  10. 像这种或者更多层级的,就需要额外处理一下 children 属性了,但是方法都是差不的,无非就是数据处理的时候多处理一下,而且一般来说就是两层,最外层第一个 router-view 来展示一级路由(比如登录、404、layout),main 区域的 router-view 展示二级路由(比如 home、my、user…)

  11. 经过这个分析之后,我们就是能确定,我们要做的事情就是,把这些获取的菜单数据,来找到对应的组件,并把这些组件添加为 layout 组件的子组件,在 main 区域展示

具体实现

配置静态路由

  1. 根据上面的粗略的分析,第一步就是创建路由,这一步非常简单,我直接粘贴代码了,如下:

    import Vue from 'vue'
    import VueRouter from 'vue-router'
    Vue.use(VueRouter)
    
    const router = new VueRouter({
    	mode: 'hash',
    	routes: []
    })
    
    export default router
    
  2. 这就是一个最基础的结构了,而在这个需求中,至少有两个路由一定是静态的,一个是 login,一个是 layout,当然通常还有个一个任意路由,表示 404,这里我就不写了,大家有时间自己添加一下就好,如下:

    import Vue from 'vue'
    import VueRouter from 'vue-router'
    Vue.use(VueRouter)
    
    const router = new VueRouter({
    	mode: 'hash',
    	routes: [
    		{
    			path: '/',
    			name: 'layout',
    			component: () => import('@/layout')
    		},
    		{
    			path: '/login',
    			name: 'login',
    			component: () => import('@/views/login')
    		}
    	]
    })
    
    export default router
    
  3. 添加两个静态路由非常简单吧,然后把这个在 main js 页面引入使用,我就不展示了

路由权限判断

  1. 上面的配置如果我们直接在浏览器中打开 http://localhost:8080/ 这个地址,那么展示的就是 layout 组件,如果需要展示位 login 组件的话,我们就需要在全局前置路由守卫上动一下手脚了

  2. 也非常简单,一个用户登没登录,就是判断是否是存在了 token,如果有就是登录了,如果没有就是没有登录,根据这个,我们可以得出一张关系图,如图:

    在这里插入图片描述

  3. 这只是一个简单的路由权限判断,具体的还需要根据业务来扩展,根据这个关系图,我们可以写出如下代码:

    import Vue from 'vue'
    import VueRouter from 'vue-router'
    Vue.use(VueRouter)
    
    import store from '@/store'
    
    const router = new VueRouter({
    	mode: 'hash',
    	routes: [
    		{
    			path: '/',
    			name: 'layout',
    			component: () => import('@/layout')
    		},
    		{
    			path: '/login',
    			name: 'login',
    			component: () => import('@/views/login')
    		}
    	]
    })
    
    router.beforeEach((to, from, next) => {
    	const token = store.state.login.token
    	if (token) {
    		if (to.path === '/login') {
    			next(false)
    		} else {
    			next()
    		}
    	} else {
    		if (to.path === '/login') {
    			next()
    		} else {
    			next('/login')
    		}
    	}
    })
    
    export default router
    

登录

  1. 实现这点的方法也不止一种,本文采用的是在 store 的 login 模块中完成登录,至于 axios 的封装或者基于 xhr 等等的请求方面,我这里不做解析了

  2. store 的基础配置不做赘述了,直接粘贴代码,如下:

    import Vue from 'vue'
    import Vuex from 'vuex'
    Vue.use(Vuex)
    
    import login from './login'
    
    const store = new Vuex.Store({
    	modules: { login }
    })
    
    export default store
    
  3. 至于 login 模块的话,书写也非常简单,编写登录函数,登录成功之后同步获取菜单数据,如下:

    import { loginApi, menuApi } from '@/api'
    import router from '@/router'
    
    export default {
    	namespaced: true,
    	state: {
    		userInfo: {} || localStorage.getItem('user_info'),
    		token: '' || localStorage.getItem('token'),
    		menuList: [] || localStorage.getItem('menu_list')
    	},
    	mutations: {
    		SET_MENU_LIST(state, payload) {
    			state.menuList = payload
    		},
    
    		SET_USER_INFO(state, payload) {
    			state.userInfo = payload
    			localStorage.setItem('user_info', JSON.stringify(payload))
    		},
    
    		SET_TOKEN(state, payload) {
    			state.token = payload
    			localStorage.setItem('token', payload)
    		},
            
            // 退出登录
    		LOG_OUT() {
    			localStorage.removeItem('token')
    			localStorage.removeItem('user_info')
    			localStorage.removeItem('menu_list')
    			// 刷新页面-因为路由权限的存在会导航到login,并且通过这个刷新可以避免重复添加路由
    			window.location.reload()
    		}
    	},
    	actions: {
    		async login({ commit }, payload) {
                // 登录请求-获取token
    			const loginResp = await loginApi.reqLogin(payload)
    			if (loginResp?.errorCode !== 0) return
    			commit('SET_USER_INFO', loginResp.data.userInfo)
    			commit('SET_TOKEN', loginResp.data.token)
    
    			// 请求菜单列表
    			const menuListResp = await menuApi.reqGetMenuList()
    			localStorage.setItem('menu_list', JSON.stringify(menuListResp.data))
    			commit('SET_MENU_LIST', menuListResp.data)
    
    			// 跳转至首页
    			router.push('/home')
    		}
    	}
    }
    
    
  4. 这部分代码还是非常简单的,在入口文件main.js 引用 store 和在登录界面收集表单数据提交调用这个 login 方法登录,大家就自己实现一下吧

  5. 现在我们获取到这个数据之后,表示我们可以完成两件事情,第一就是渲染侧边的菜单列表,第二就是根据这个来添加正确的动态路由

  6. 渲染菜单列表没有什么好说的,如果没有菜单栏的递归需求的话,菜单栏直接 cv 组件库的代码即可,需要递归的话就要自己封装一下了

添加动态路由

  1. 要添加动态路由,需要有两个数据,一个是远程获取的菜单数据,一个是前端的映射的组件关系。远程数据已经有了,前端映射的组件关系,就看你自己的业务来配置了,还是非常简单的,把你前端需要展示的页面都在一个 js 文件引入就好了,如下:

    export default [
    	{
    		name: 'home',
    		component: () => import('@/views/home')
    	},
    	{
    		name: 'my',
    		component: () => import('@/views/my')
    	},
    	{
    		name: 'device-add',
    		component: () => import('@/views/device/add')
    	},
    	{
    		name: 'device-list',
    		component: () => import('@/views/device/list')
    	},
    	{
    		name: 'user-add',
    		component: () => import('@/views/user/add')
    	},
    	{
    		name: 'user-list',
    		component: () => import('@/views/user/list')
    	}
    ]
    
  2. 具体需要多少配置项,就视个人业务而定,我这里使用 name 匹配,你也可以是 path 或者其他属性

  3. 在看一下远程的数据具体是什么样的,有助于理解,如下:

    [
        {
            "id": 1,
            "name": "home",
            "path": "/home",
            "nickname": "首页",
            "type": 2,
            "order": 1,
            "parentId": 0,
            "icon": "icon-tubiao_shouye-",
            "children": null
        },
        {
            "id": 2,
            "name": "device",
            "path": "/device",
            "nickname": "设备管理",
            "type": 1,
            "order": 2,
            "parentId": 0,
            "icon": "icon-guanli",
            "children": [
                {
                    "id": 3,
                    "name": "device-list",
                    "path": "/device/list",
                    "nickname": "设备列表",
                    "type": 2,
                    "order": 1,
                    "parentId": 2,
                    "icon": "icon-xuanzeweixuanze",
                    "children": null
                },
                {
                    "id": 4,
                    "name": "device-add",
                    "path": "/device/add",
                    "nickname": "设备添加",
                    "type": 2,
                    "order": 2,
                    "parentId": 2,
                    "icon": "icon-xuanzeweixuanze",
                    "children": null
                }
            ]
        },
        {
            "id": 5,
            "name": "my",
            "path": "/my",
            "nickname": "个人中心",
            "type": 2,
            "order": 3,
            "parentId": 0,
            "icon": "icon-xiazai",
            "children": null
        },
        {
            "id": 6,
            "name": "user",
            "path": "/user",
            "nickname": "用户管理",
            "type": 1,
            "order": 4,
            "parentId": 0,
            "icon": "icon-yonghuguanli",
            "children": [
                {
                    "id": 7,
                    "name": "user-list",
                    "path": "/user-list",
                    "nickname": "用户列表",
                    "type": 2,
                    "order": 1,
                    "parentId": 6,
                    "icon": "icon-xuanzeweixuanze",
                    "children": null
                },
                {
                    "id": 8,
                    "name": "user-add",
                    "path": "/user-add",
                    "nickname": "用户添加",
                    "type": 2,
                    "order": 2,
                    "parentId": 6,
                    "icon": "icon-xuanzeweixuanze",
                    "children": null
                }
            ]
        }
    ]
    
  4. 剩下的就是递归遍历的找出组装出对应的 route 配置的事情了,那么我们需要有这样的一个函数,来帮助我们完成这件事情,代码如下:

    import router from '@/router'
    
    // 前端映射的组件关系配置
    import routeConfig from '@/router/route-config'
    
    export default function (menuList) {
    	const routeList = []
    	const deepMenu = menuList => {
    		for (const menu of menuList) {
    			if (menu.children && menu.children.length > 0) {
    				deepMenu(menu.children)
    			} else {
    				const item = routeConfig.find(item => item.name === menu.name)
    				if (!item) return
    				// 去掉第一项斜杠-子路由 path 属性不需要携带开头的 /
    				const path = menu.path.replace(/^\//, '')
                    // 路由元信息可以帮助我们完成一些其他操作的时候,需要的一些辅助数据
    				routeList.push({ ...item, path, meta: { title: menu.nickname } })
    			}
    		}
    	}
    	deepMenu(menuList)
    
    	for (const route of routeList) {
            // 遍历添加路由
    		router.addRoute('layout', route)
    	}
    }
    
  5. 有了这个方法之后,自然就是使用,如下:

    import { loginApi, menuApi } from '@/api'
    import router from '@/router'
    import menuToRoute from '@/utils/menu-to-route'
    
    export default {
    	namespaced: true,
    	state: {
    		userInfo: {} || localStorage.getItem('user_info'),
    		token: '' || localStorage.getItem('token'),
    		menuList: [] || localStorage.getItem('menu_list')
    	},
    	mutations: {
    		SET_MENU_LIST(state, payload) {
    			state.menuList = payload
                // 调用菜单转路由方法
    			menuToRoute(payload)
    		},
    
    		SET_USER_INFO(state, payload) {
    			state.userInfo = payload
    			localStorage.setItem('user_info', JSON.stringify(payload))
    		},
    
    		SET_TOKEN(state, payload) {
    			state.token = payload
    			localStorage.setItem('token', payload)
    		},
    		
            // 退出登录
    		LOG_OUT() {
    			localStorage.removeItem('token')
    			localStorage.removeItem('user_info')
    			localStorage.removeItem('menu_list')
    			// 刷新页面-因为路由权限的存在会导航到login,并且通过这个刷新可以避免重复添加路由
    			window.location.reload()
    		}
    	},
    	actions: {
    		async login({ commit }, payload) {
    			const loginResp = await loginApi.reqLogin(payload)
    			if (loginResp?.errorCode !== 0) return
    			commit('SET_USER_INFO', loginResp.data.userInfo)
    			commit('SET_TOKEN', loginResp.data.token)
    
    			// 请求菜单列表
    			const menuListResp = await menuApi.reqGetMenuList()
    			localStorage.setItem('menu_list', JSON.stringify(menuListResp.data))
    			commit('SET_MENU_LIST', menuListResp.data)
    
    			// 跳转至首页
    			router.push('/home')
    		}
    	}
    }
    
  6. 此时我们已经完成了整个效果的实现,当然还有一个问题,但是这个问题后面再说,先看一下效果,如图:

    在这里插入图片描述

    在这里插入图片描述

  7. 可以看到,不同的账户登录会因为角色不同展现的菜单也不同

修复刷新路由丢失问题

  1. 现在我们这个看着没什么问题,是因为我们没有点击刷新,先看看问题,如图:

    在这里插入图片描述

  2. 一旦刷新之后就会导致动态路由清空,但是又没有重新注册添加,自然就会找不到这个路由了,因此白屏就很正常了

  3. 解决也非常简单,在每次刷新的时候,都在重新注册一次动态路由就好了,所以在 store 的 login 模块多添加一个方法,如下:

    import { loginApi, menuApi } from '@/api'
    import router from '@/router'
    import menuToRoute from '@/utils/menu-to-route'
    
    export default {
    	namespaced: true,
    	state: {
    		userInfo: {} || localStorage.getItem('user_info'),
    		token: '' || localStorage.getItem('token'),
    		menuList: [] || localStorage.getItem('menu_list')
    	},
    	mutations: {
    		SET_MENU_LIST(state, payload) {
    			state.menuList = payload
    			menuToRoute(payload)
    		},
    
    		SET_USER_INFO(state, payload) {
    			state.userInfo = payload
    			localStorage.setItem('user_info', JSON.stringify(payload))
    		},
    
    		SET_TOKEN(state, payload) {
    			state.token = payload
    			localStorage.setItem('token', payload)
    		},
    
    		LOG_OUT() {
    			localStorage.removeItem('token')
    			localStorage.removeItem('user_info')
    			localStorage.removeItem('menu_list')
    			window.location.reload()
    		}
    	},
    	actions: {
    		async login({ commit }, payload) {
    			const loginResp = await loginApi.reqLogin(payload)
    			if (loginResp?.errorCode !== 0) return
    			commit('SET_USER_INFO', loginResp.data.userInfo)
    			commit('SET_TOKEN', loginResp.data.token)
    
    			const menuListResp = await menuApi.reqGetMenuList()
    			localStorage.setItem('menu_list', JSON.stringify(menuListResp.data))
    			commit('SET_MENU_LIST', menuListResp.data)
    
    			router.push('/home')
    		},
    
    		// 加载本地数据
    		async loadLocal({ commit }) {
    			const menuList =  localStorage.getItem('menu_list')
    			if (menuList) {
    				commit('SET_MENU_LIST', JSON.parse(menuList))
    			}
    		}
    	}
    }
    
  4. loadLocal 这个方法还可以初始化一下其他你需要初始化的信息,包括但不限于这个菜单列表,其他是导出这个方法,让其他人使用,可以直接从这个模块使用,也可以其他地方导出,我这里就在 store/index.js 文件下导出,如下:

    import Vue from 'vue'
    import Vuex from 'vuex'
    Vue.use(Vuex)
    
    import login from './login'
    
    const store = new Vuex.Store({
    	modules: { login }
    })
    
    // 导出方法
    export function loadLocal() {
    	store.dispatch('login/loadLocal')
    }
    
    export default store
    
  5. 最后在 main.js 中调用此方法即可,导入和使用语句如下:

    import { loadLocal } from './store'
    
    loadLocal()
    
  6. 现在我们在来看看效果,如图:

    在这里插入图片描述

结语

这里只是给大家展示一种思路,具体的实现需要根据自己的业务来定,但是整体的流程都是差不多的

如果对于这个递归菜单,和后端部分这个实现登录逻辑部分,可以查看我后续发布的其他文章,或者如果我没忘记的话,我会来这里补上查看链接

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/314671.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

以报时机器人为例详细介绍tracker_store和event_broker

报时机器人源码参考[1][2],本文重点介绍当 tracker_store 类型为 SQL 时,events 表的表结构以及数据是如何生成的。以及当 event_broker 类型为 SQL 时,events 表的表结构以及数据是如何生成的。 一.报时机器人启动 [3] Rasa 对话系统启动方…

解决命令行无法启动scrapy爬虫

前言 最近在准备毕设项目,想使用scrapy架构来进行爬虫,找了一个之前写过的样例,没想到在用普通的启动命令时报错。报错如下 无法将“scrapy”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径…

最大公共子串

解题思路: 解题代码: UP主运用的方法很巧妙。厉害。

Chrome 浏览器插件从 Manifest V2 升级到 V3 版本所需要修改的点

一、Manifest V2 支持时间表 Chrome 浏览器官方已经给出确定的时间来弃用 V2 版本的插件了。 最早从 2024 年 6 月的 Chrome 127 开始,我们将开始停用 Chrome 的不稳定版本(开发者版、Canary 版和 Beta 版)中的 Manifest V2 扩展程序。受此变…

MySQL入门:DCL数据控制语言(管理用户,权限控制),MySQL函数(字符串,数值,日期,流程)

目录 1.DCL(数据控制语言)1.管理用户2.权限控制 2.函数1.字符串函数2.数值函数3.日期函数4.流程函数 1.DCL(数据控制语言) DCL英文全称是Data ControlLanguage(数据控制语言),用来管理数据库用户、控制数据库的访问权限…

vivado 使用项目摘要、配置项目设置、仿真设置

使用项目摘要 Vivado IDE包括一个交互式项目摘要,可根据设计动态更新命令被运行,并且随着设计在设计流程中的进展。项目摘要包括概览选项卡和用户可配置的仪表板,如下图所示。有关信息,请参阅《Vivado Design Suite用户指南&…

轻松上手Linux文件操作:五种方法教你创建文件

轻松上手Linux文件操作:五种方法教你创建文件 一、引言二、使用touch命令创建文件三、使用文本编辑器创建文件四、使用echo命令创建文件五、使用cat命令创建文件六、使用重定向符号创建文件七、总结 一、引言 本文介绍五种在Linux系统中创建文件的方法,…

PaaS服务的零代码开发平台——JNPF

目前市场上低代码平台鱼龙混杂,真正能满足企业复杂业务(ERP、MES等)的平台不多,这里推荐一款好用、靠谱、性价比较高的低代码平台:JNPF开发平台。 JNPF开发平台是一款PaaS服务为核心的零代码开发平台,集成了…

Go 如何处理死锁以提供哪些工具来检测或防死锁?

并发是 Go 的核心特性,它使程序能够同时处理多个任务。它是现代编程的一个强大组件,如果使用正确,可以产生高效、高性能的应用程序。然而,并发性也带来了顺序编程中不存在的某些类型错误的可能性,其中最臭名昭著的是死…

双指针问题——求只包含两个元素的最长连续子序列(子数组)

一,题目描述 你正在探访一家农场,农场从左到右种植了一排果树。这些树用一个整数数组 fruits 表示,其中 fruits[i] 是第 i 棵树上的水果 种类 。 你想要尽可能多地收集水果。然而,农场的主人设定了一些严格的规矩,你必…

Istio安装和基础原理

1、Istio简介 Istio 是一个开源服务网格,它透明地分层到现有的分布式应用程序上。 Istio 强大的特性提供了一种统一和更有效的方式来保护、连接和监视服务。 Istio 是实现负载平衡、服务到服务身份验证和监视的路径——只需要很少或不需要更改服务代码。它强大的控…

LeetCode刷题.14(不用算法解决1557. 可以到达所有点的最少点数目)

给你一个 有向无环图 , n 个节点编号为 0 到 n-1 ,以及一个边数组 edges ,其中 edges[i] [fromi, toi] 表示一条从点 fromi 到点 toi 的有向边。 找到最小的点集使得从这些点出发能到达图中所有点。题目保证解存在且唯一。 你可以以任意顺…

Python跑pytorch程序抢占公共GPU自动运行脚本

问题描述 当我们有一个服务器,服务器上面有4-5个GPU,那么我们需要时刻看哪个GPU空着,当发现服务器空闲了,我们就可以跑自己的深度学习了。 然而,人盯着总是费时费力的,所以可以让Python看到哪个GPU空闲就…

Windows使用(版本8.11)ElasticSearch、elasticsearch-head、kibana

下载安装引用这篇文章 目录 1、ES基本知识核心术语核心概念倒排索引ES字典树ES怎么保证读写一致 2、Window启动ES步骤elasticsearch-8.11.3elasticsearch-head-masterkibana-8.11.3 3、Kibana 调用ES API示例 1、ES基本知识 核心术语 ● 索引:index (相…

centos 7.6 忘记root密码 怎么重置root密码

centos 7.6 忘记root密码 怎么重置root密码 1、 问题描述2、解决方法 1、 问题描述 centos 7.6 忘记root密码,登录不了root用户 2、解决方法 启动系统进入grub界面,按e进入编辑模式,找到含有quiet的这行。在这行最后 添加 rw init/bin/ba…

基础数据结构之堆栈

堆栈的定义、入栈、出栈、查询栈顶 #include <stdio.h> #include <stdlib.h>typedef int DataType;// 定义栈节点结构体 struct StackNode;struct StackNode {DataType data; // 节点数据struct StackNode* next; // 指向下一个节点的指针 };// 定…

Python基础知识:整理12 JSON数据格式的转换

首先导入python中的内置包json import json 1 准备一个列表&#xff0c;列表内每个元素都是字典&#xff0c;将其转换为JSON 使用json.dumps()方法 data [{"name": "John", "age": 30}, {"name": "Jane", "age":…

NAND系统性能提升常见方案

随着NAND的发展&#xff0c;针对NAND系统性能提升&#xff0c;业内目前主要的做法有以下几种方案&#xff1a; 1.提升总线频率和优化AC时序&#xff1a; 提高NAND闪存接口的工作频率可以显著加快数据传输速度。通过不断改进工艺和技术&#xff0c;缩短了信号稳定时间、降低了延…

逆向分析爬取网页动态

本例子以爬取人民邮电出版社网页新书的信息为例 由于页面是动态的&#xff0c;信息会不停地更新&#xff0c;所以不同时间的爬取结果会不同。

Selenium+Remote WebDriver+python脚本访问示例

一、环境要求&#xff1a; 1、selenium-server安装&#xff0c;下载地址&#xff1a;Release Selenium 4.16 SeleniumHQ/selenium GitHub 2、python3及pycharm 二、启动selenium-server 下载selenium-server之后&#xff0c;解压到D:\selenium-server目录&#xff0c;然后…