当前位置: 首页 > news >正文

实战指南:Vue 2基座 + Vue 3 + Vite + TypeScript微前端架构实现动态菜单与登录共享

实战指南:Vue 2基座 + Vue 3 + Vite + TypeScript子应用+vue2微前端架构实现动态菜单与登录共享

导读:

在当今的前端开发中,微前端架构已经成为了一种流行的架构模式。本文将介绍如何结合Vue 2基座Vue 3子应用Vite构建工具和TypeScript语言,利用qiankun微前端框架实现动态菜单和登录共享功能的实战指南。

效果:

qiankun基座实现动态菜单
实战指南:Vue 2基座 + Vue 3 + Vite + TypeScript微前端架构实现动态菜单与登录共享

引言

当前项目架构,vue+ruoyi+elementUI作为基座,vue3+recoDesign+vite+ts作为子应用,基座只用于登录鉴权,动态菜单功能,尽量少在基座写其他业务,子应用分为业务子应用,系统子应用,其他业务子应用,结合npm私服组件库进行组件抽取,供各个子应用使用,基座登录后,将token及其其他子应用需要的参数通过props进行传递比如最直接的【按钮权限,token】。

技术栈介绍

  1. vue2全家桶+ruoyi脚手架进行基座改造。
  2. vue3全家桶+arcoDesign中台后台脚手架进行子应用改造。

vue2子应用改造,vue3子应用改造
http://t.csdnimg.cn/U7a3p,vue2+qiankun项目实战
http://t.csdnimg.cn/4UFDs,vue3+qiankun项目实战

  1. npm私服组件库的打包上传拉取使用:http://t.csdnimg.cn/5Tgax。

创建vue2基座

  1. 安装qiankun
    npm i qiankun -S
    
  2. 设置需要将子应用的页面嵌入到主应用的某个div中(子应用在主应用上的渲染出口)<div id="subapp-container">
    实战指南:Vue 2基座 + Vue 3 + Vite + TypeScript微前端架构实现动态菜单与登录共享
  3. 在主应用(基座)中注册子应用
    实战指南:Vue 2基座 + Vue 3 + Vite + TypeScript微前端架构实现动态菜单与登录共享
    整体代码:
import Vue from 'vue'
import Cookies from 'js-cookie'
import Element from 'element-ui'
import './assets/styles/element-variables.scss'
import { registerMicroApps, start, setDefaultMountApp } from 'qiankun';
import "ant-design-vue/dist/antd.less"
import 'default-passive-events'
import '@/assets/styles/index.scss' // global css
import '@/assets/styles/ruoyi.scss' // ruoyi css
import App from './App'
import store from './store'
import router from './router'
import directive from './directive' // directive
import plugins from './plugins' // plugins
import { download } from '@/utils/request'
import './assets/icons' // icon
import './permission' // permission control
import { getDicts } from "@/api/system/dict/data";
import { getConfigKey } from "@/api/system/config";
import { parseTime, resetForm, addDateRange, selectDictLabel, selectDictLabels, handleTree } from "@/utils/ruoyi";
// 分页组件
import Pagination from "@/components/Pagination";
// 自定义表格工具组件
import RightToolbar from "@/components/RightToolbar"
// 富文本组件
import Editor from "@/components/Editor"
// 文件上传组件
import FileUpload from "@/components/FileUpload"
// 图片上传组件
import ImageUpload from "@/components/ImageUpload"
// 图片预览组件
import ImagePreview from "@/components/ImagePreview"
// 字典标签组件
import DictTag from '@/components/DictTag'
// 头部标签组件
import VueMeta from 'vue-meta'
// 字典数据组件
import DictData from '@/components/DictData'
//局部使用antDesign-vue中的tree组件
import { Tree } from 'ant-design-vue';
import { Table } from 'ant-design-vue';
import { Icon } from 'ant-design-vue';
// import a from "hskCommApi"
Vue.config.productionTip = false;
// 全局方法挂载
Vue.prototype.getDicts = getDicts
Vue.prototype.getConfigKey = getConfigKey
Vue.prototype.parseTime = parseTime
Vue.prototype.resetForm = resetForm
Vue.prototype.addDateRange = addDateRange
Vue.prototype.selectDictLabel = selectDictLabel
Vue.prototype.selectDictLabels = selectDictLabels
Vue.prototype.download = download
Vue.prototype.handleTree = handleTree// 全局组件挂载
Vue.component('DictTag', DictTag)
Vue.component('Pagination', Pagination)
Vue.component('RightToolbar', RightToolbar)
Vue.component('Editor', Editor)
Vue.component('FileUpload', FileUpload)
Vue.component('ImageUpload', ImageUpload)
Vue.component('ImagePreview', ImagePreview)
Vue.component('ATree', Tree)
Vue.component('ATable', Table)
Vue.component('AIcon', Icon)Vue.use(directive)
Vue.use(plugins)
Vue.use(VueMeta)
DictData.install()Vue.use(Element, {size: Cookies.get('size') || 'medium' // set element-ui default size
})
// 1. 注册微应用 
registerMicroApps([{name: 'son',entry: process.env.VUE_APP_BUSINESS, // 子应用页面访问入口container: '#subapp-container', // 子应用渲染的出口activeRule: '/vision-web/business-module-vue2', // 路径匹配规则sandbox: {strictStyleIsolation: true, // 开启样式隔离},props: { sharedStore: store, baseName: '/vision-web/business-module-vue2' }},{name: 'son2',entry: process.env.VUE_APP_SYSTEM_URL, // 子应用页面访问入口container: '#subapp-container', // 子应用渲染的出口activeRule: '/vision-web/system-module-vue2', // 路径匹配规则sandbox: {strictStyleIsolation: true, // 开启样式隔离},props: { sharedStore: store, baseName: '/vision-web/system-module-vue2' }},{name: 'business-module-vue3',  // 微应用package.json的name字段entry: '//192.168.80.15:8010/business-module-vue3/', // 微应用访问地址,默认加载这个html页面并解析其中的js动态执行container: '#subapp-container', // 子应用渲染的出口// return location.pathname.includes('/vite-vue3-app2') activeRule: '/vision-web/business-module-vue3',// 激活路径,微应用路由sandbox: {strictStyleIsolation: false, // 开启样式隔离},props: { sharedStore: store, baseName: '/vision-web/business-module-vue3' }},
])
// 判断subapp-container是否已加载,如果未加载就延迟
function ensureContainerAndStartMicroApps() {if (document.getElementById('subapp-container')) {// 容器存在,可以注册微应用并启动// registerMicroApps([...]); // 注册微应用的代码setDefaultMountApp('/'); // 默认打开的子应用start({sandbox: {// strictStyleIsolation: true,experimentalStyleIsolation: true}}); // 启动 qiankun} else {// 容器尚不存在,稍后重试setTimeout(ensureContainerAndStartMicroApps, 100); // 100毫秒后再次尝试}
}// 确保 DOMContentLoaded 事件触发后再执行
document.addEventListener('DOMContentLoaded', ensureContainerAndStartMicroApps);
Vue.config.productionTip = falsenew Vue({el: '#app',router,store,render: h => h(App)
})
  1. 基座改造完成后,进行子应用的改造

在根目录下创建一个子应用,,子应用最好与在基座主应用main.js中配置的名称一致,这样可以直接使用package.json中的name作为output。
vue.config.js,devServer的端口改为与主应用配置的一致,且加上跨域headers和output配置。
实战指南:Vue 2基座 + Vue 3 + Vite + TypeScript微前端架构实现动态菜单与登录共享

配置子应用支持跨域
实战指南:Vue 2基座 + Vue 3 + Vite + TypeScript微前端架构实现动态菜单与登录共享
进行微应用打包成UMD库格式
实战指南:Vue 2基座 + Vue 3 + Vite + TypeScript微前端架构实现动态菜单与登录共享

设置vue.config.js中的publicPath,防止出现主应用引入子应用的时候出现样式,图片访问不到情况
在这里插入图片描述

新增src/public-path.js

if (window.__POWERED_BY_QIANKUN__) {__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;}

src/router/index.js改为只暴露routesnew Router改到 main.js中声明,并改造main.js,并引入src下创建的public-path.js,改写render,添加生命周期函数,最终结果如下⬇,当前是子应用的时候根据主应用传递过来的baseName进行子应用路由的base及其mode的改造。

import './public-path';import Vue from 'vue'import Cookies from 'js-cookie'import Element from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css';
import './assets/styles/element-variables.scss'// import 'ant-design-vue/dist/antd.css';import "ant-design-vue/dist/antd.less"import '@/assets/styles/index.scss' // global css
import '@/assets/styles/ruoyi.scss' // ruoyi css
import App from './App'
import store from './store'
import router, {constantRoutes} from './router'
import directive from './directive' // directive
import plugins from './plugins' // plugins
//引入hsk组件
import hskui from "hsk-ui"
import "hsk-ui/styles/hskui.css"//引入hsk方法
import { hskMsgbox } from 'hsk-ui/commonUtils'
import { download } from '@/utils/request'import './assets/icons' // icon
import './permission' // permission control
import { getDicts } from "@/api/system/dict/data";
import { getConfigKey } from "@/api/system/config";
import { parseTime, resetForm, addDateRange, selectDictLabel, selectDictLabels, handleTree } from "@/utils/ruoyi";
// 分页组件
import Pagination from "@/components/Pagination";
// Vue.prototype.hskMsgbox = hskui.hskMsgbox.hskMsgbox
// 自定义表格工具组件
import RightToolbar from "@/components/RightToolbar"
// 富文本组件
import Editor from "@/components/Editor"
// 文件上传组件
import FileUpload from "@/components/FileUpload"
// 图片上传组件
import ImageUpload from "@/components/ImageUpload"
// 图片预览组件
import ImagePreview from "@/components/ImagePreview"
// 字典标签组件
import DictTag from '@/components/DictTag'
// 头部标签组件
import VueMeta from 'vue-meta'
// 字典数据组件
import DictData from '@/components/DictData'
// import action from '../src/action'
//局部使用antDesign-vue中的tree组件
import { Tree } from 'ant-design-vue';
import { Table } from 'ant-design-vue';
import { Icon } from 'ant-design-vue';
import Router from "vue-router";
// 全局方法挂载
Vue.prototype.hskMsgbox = hskMsgbox
Vue.prototype.getDicts = getDicts
Vue.prototype.getConfigKey = getConfigKey
Vue.prototype.parseTime = parseTime
Vue.prototype.resetForm = resetForm
Vue.prototype.addDateRange = addDateRange
Vue.prototype.selectDictLabel = selectDictLabel
Vue.prototype.selectDictLabels = selectDictLabels
Vue.prototype.download = download
Vue.prototype.handleTree = handleTree// 全局组件挂载
Vue.component('DictTag', DictTag)
Vue.component('Pagination', Pagination)
Vue.component('RightToolbar', RightToolbar)
Vue.component('Editor', Editor)
Vue.component('FileUpload', FileUpload)
Vue.component('ImageUpload', ImageUpload)
Vue.component('ImagePreview', ImagePreview)
Vue.component('ATree', Tree)
Vue.component('ATable', Table)
Vue.component('AIcon', Icon)Vue.use(directive)
Vue.use(plugins)
Vue.use(VueMeta)
DictData.install()/*** If you don't want to use mock-server* you want to use MockJs for mock api* you can execute: mockXHR()** Currently MockJs will be used in the production environment,* please remove it before going online! ! !*/Vue.use(Element, {size: Cookies.get('size') || 'medium' // set element-ui default size
})
Vue.use(hskui)
let instance = null
Cookies.set("client_id","admin")
async function render(props={}){const { container } = props;instance = new Vue({router,store,render: h => h(App),beforeCreate(){if (window.__POWERED_BY_QIANKUN__) {store.state.user = props.sharedStore.state.user}}}).$mount(container?container.querySelector('#app')  //渲染到主应用的入口:'#app' //独立运行的时候)
}// 在被qiankun引用时 修改运行时的 `publicPath`
if (window.__POWERED_BY_QIANKUN__) {__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
//如果独立运行的时候,判断是否是独立运行
if (!window.__POWERED_BY_QIANKUN__) {render();
}/*** 子应用建议使用qiankun的规则来接入不需要安装任何依赖,* 只需要再三个入口到二u三个必须的钩子函数给qiankun主应用使用* 钩子函数必须返回promise(启动的时候调用)*/
export async function bootstrap() {// console.log('[vue] vue app bootstraped');
}
// 从生命周期 mount 中获取通信方法,props默认会有onGlobalStateChange和setGlobalState两个api
export async function mount(props) {// console.log('乾坤子应用容器加载完成,开始渲染 child',props)if (window.__POWERED_BY_QIANKUN__) {if(router.options.base !== props.baseName){const { container } = props;// 获取容器元素,用于后续操作或设置环境变量等let rootRoute = new Router({mode: 'history', // 去掉url中的#base: props.baseName,scrollBehavior: () => ({y: 0}),routes: constantRoutes})instance =  new Vue({router:rootRoute,store,render: h => h(App),beforeCreate(){if (window.__POWERED_BY_QIANKUN__) {store.state.user = props.sharedStore.state.user}}}).$mount(container?container.querySelector('#app')  //渲染到主应用的入口:'#app' //独立运行的时候)} else {render(props);}} else {render(props);}
}
/*** 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效*/
export async function update(props) {
}export async function unmount() {instance.$destroy();instance.$el.innerHTML = '';instance = null;
}
export default instance;

上面完成进行基座的路由配置,使其主应用能够通过路由访问子应用。

{path: '/system-module-vue2/system',component: Layout,hidden: false,redirect: 'noredirect',meta: {title: "系统设置",noCache: false,link: null,icon: "system"},children: [{path: 'user',name: 'user',meta: { title: '用户管理', icon: 'user', "link": null }}, {name: "role",path: "role",meta: {"title": "角色管理","icon": 'tree',"noCache": false,"link": null}}, {name: "codeManagement",path: "codeManagement",meta: {"title": "编码管理","icon": 'tree',"noCache": false,"link": null}},{name: "log",path: "log",meta: {title: "操作日志",icon: 'log',link: null}},{name: "dictionaryMiddle",path: "dictionaryMiddle",meta: {title: "数据字典管理",icon: 'component',link: null}}, {path: 'configInformation',hidden: true,name: 'configInformation',meta: { title: '配置信息', icon: '', noCache: true }}, {name: "templateMiddle",path: "templateMiddle",meta: {title: "消息模板管理",icon: 'message',link: null}}, {name: "serverLog",path: "serverLog",meta: {title: "服务调用日志",icon: 'log',link: null}}, {name: "serverLogDetail",path: "serverLogDetail",hidden: true,meta: {title: "服务使用情况",icon: 'log',link: null}}]},

效果:其实配置最难的地方就是路由的配置。
实战指南:Vue 2基座 + Vue 3 + Vite + TypeScript微前端架构实现动态菜单与登录共享
主应用登陆后通过qiankun自带的propstoken传递给子应用,子应用在qiankunmount生命周期中设置token进行响应的判断设置。我当前主子应用用的都是ruoyi脚手架搭建的,我直接将store传递个子应用使用,不需要做太多的操作即可。实战指南:Vue 2基座 + Vue 3 + Vite + TypeScript微前端架构实现动态菜单与登录共享
在这里插入图片描述
配置动态路由:我当前是ruoyi脚手架搭建的后台项目,配置动态路由在permission.js中设置,和后端约定好后,调用接口,通过后端返回进行路由设置。
实战指南:qiankun前端架构实现动态菜单与登录共享
动态菜单全部代码,目前是写死的,后期根据getRouters()方法向后端发送请求进行动态配置,注意:路由基本上前端进行配置,不然很容易出现404报错现象。
下面是permission.js文件代码,其中当前后端还未有接口,目前先使用adminId代表不同系统显示不同路由

import auth from '@/plugins/auth'
import router, { constantRoutes, dynamicRoutes } from '@/router'
import { getRouters } from '@/api/menu'
import Layout from '@/layout/index'
import ParentView from '@/components/ParentView'
import InnerLink from '@/layout/components/InnerLink'
import store from '../../store'const permission = {state: {routes: [],addRoutes: [],defaultRoutes: [],topbarRouters: [],// sidebarRouters: []sidebarRouters: [],permissions: [],},mutations: {SET_PERMISSIONS: (state, permissions) => {state.permissions = permissions},SET_ROUTES: (state, routes) => {state.addRoutes = routesstate.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 res1 = {"msg": "操作成功","code": 200,"data": [{"path": "/business-module-vue2/equipment","redirect": "noRedirect","component": "Layout","meta": {"title": "检测设备管理","noCache": false,"icon": 'zhgl',"link": null},"children": [{"name": "equipment","path": "/business-module-vue2/equipment/equipment","hidden": false,"redirect": "noRedirect","meta": {"title": "检测设备管理","icon": 'yygl',"noCache": false,"link": null}},]}]}const res2 = {"msg": "操作成功","code": 200,"data": [{"path": "/business-module-vue2","redirect": "noRedirect","component": "Layout","meta": {"title": "业务系统","noCache": false,"icon": 'zhgl',"link": null},"children": [{"name": "zhanghao","path": "/business-module-vue2/zhanghao","hidden": false,"redirect": "noRedirect","meta": {"title": "账号管理","icon": 'zhgl',"noCache": false,"link": null}},{"name": "tenant","path": "/business-module-vue2/tenant","hidden": false,"redirect": "noRedirect","meta": {"title": "企业管理","icon": 'zhgl',"noCache": false,"link": null}},{"name": "shenhe","path": "/business-module-vue2/shenhe","hidden": false,"redirect": "noRedirect","meta": {"title": "认证审核","icon": 'zhgl',"noCache": false,"link": null}},]},{"path": "/business-module-vue2/productListA","redirect": "noRedirect","component": "Layout","meta": {"title": "资源中心","noCache": false,"icon": 'zhgl',"link": null},"children": [{"name": "productList","path": "/business-module-vue2/productList","hidden": false,"redirect": "noRedirect","meta": {"title": "产品列表","icon": 'zhgl',"noCache": false,"link": null}},{"name": "resourceList","path": "/business-module-vue2/resourceList","hidden": false,"redirect": "noRedirect","meta": {"title": "资源列表","icon": 'zhgl',"noCache": false,"link": null}},{"name": "viewProductDetail","path": "/business-module-vue2/viewProductDetail","hidden": true,"redirect": "noRedirect","meta": {"title": "产品详情","icon": 'zhgl',"noCache": false,"link": null}},{"name": "productDetail","path": "/business-module-vue2/productDetail","hidden": true,"redirect": "noRedirect","meta": {"title": "产品详情","icon": 'zhgl',"noCache": false,"link": null}},]},{"path": "/system-module-vue2/system","redirect": "noRedirect","component": "Layout","meta": {"title": "系统设置","noCache": false,"icon": 'zhgl',"link": null},"children": [{"name": "user","path": "/system-module-vue2/system/user","hidden": false,"redirect": "noRedirect","meta": {"title": "用户管理","icon": 'zhgl',"noCache": false,"link": null}},{"name": "role","path": "/system-module-vue2/system/role","hidden": false,"redirect": "noRedirect","meta": {"title": "角色管理","icon": 'zhgl',"noCache": false,"link": null}},{"name": "codeManagement","path": "/system-module-vue2/system/codeManagement","hidden": false,"redirect": "noRedirect","meta": {"title": "编码管理","icon": 'zhgl',"noCache": false,"link": null}},{"name": "log","path": "/system-module-vue2/system/log","hidden": false, "redirect": "noRedirect","meta": {"title": "操作日志","icon": 'zhgl',"noCache": false,"link": null}},{"name": "dictionaryMiddle","path": "/system-module-vue2/system/dictionaryMiddle","hidden": false, "redirect": "noRedirect","meta": {"title": "数据字典管理","icon": 'zhgl',"noCache": false,"link": null}},{"name": "configInformation","path": "/system-module-vue2/system/configInformation","hidden": false, "redirect": "noRedirect","meta": {"title": "配置信息","icon": 'zhgl',"noCache": false,"link": null}},{"name": "templateMiddle","path": "/system-module-vue2/system/templateMiddle","hidden": false, "redirect": "noRedirect","meta": {"title": "消息模板管理","icon": 'zhgl',"noCache": false,"link": null}},{"name": "serverLog","path": "/system-module-vue2/system/serverLog","hidden": false, "redirect": "noRedirect","meta": {"title": "服务调用日志","icon": 'zhgl',"noCache": false,"link": null}},{"name": "serverLogDetail","path": "/system-module-vue2/system/serverLogDetail","hidden": false, "redirect": "noRedirect","meta": {"title": "服务使用情况","icon": 'zhgl',"noCache": false,"link": null}},]},{"path": "/business-module-vue2/gatewayAdministration","redirect": "noRedirect","component": "Layout","meta": {"title": "物联网中心","noCache": false,"icon": 'zhgl',"link": null},"children": [{"name": "gatewayAdministration","path": "/business-module-vue2/gatewayAdministration","hidden": false,"redirect": "noRedirect","meta": {"title": "网关管理","icon": 'zhgl',"noCache": false,"link": null}},]},]}console.log("store",store.getters.adminID)let res = {}if(localStorage.getItem('adminId') === '1'){res = res2}else{res = res1}//遍历菜单树,将菜单树下的所有按钮权限拿到,并使用v-permissions方法比对是否有按钮权限// commit('SET_PERMISSIONS', getAllPermissions(res.data,[])) const sdata = JSON.parse(JSON.stringify(res.data))const rdata = JSON.parse(JSON.stringify(res.data))const sidebarRoutes = filterAsyncRouter(sdata)const rewriteRoutes = filterAsyncRouter(rdata, false, true)const asyncRoutes = filterDynamicRoutes(dynamicRoutes);rewriteRoutes.push({ path: '*', redirect: '/404', hidden: true })router.addRoutes(asyncRoutes);commit('SET_ROUTES', rewriteRoutes)commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(sidebarRoutes))commit('SET_DEFAULT_ROUTES', sidebarRoutes)commit('SET_TOPBAR_ROUTES', sidebarRoutes)resolve(rewriteRoutes)// })})}}
}function getAllPermissions(tree, result) {//遍历树  获取id数组for (let i = 0; i < tree.length; i++) {if (tree[i].meta.permission !== null) {result.push(...tree[i].meta.permission)}if (typeof (tree[i].children) !== "undefined" && tree[i].children !== null && tree[i].children.length > 0) {getAllPermissions(tree[i].children, result);}}return result;
}
// 遍历后台传来的路由字符串,转换为组件对象
function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {return asyncRouterMap.filter(route => {if (type && route.children) {route.children = filterChildren(route.children)}if (route.component) {// Layout ParentView 组件特殊处理if (route.component === 'Layout') {route.component = Layout} else if (route.component === 'ParentView') {route.component = ParentView} else if (route.component === 'InnerLink') {route.component = 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) {console.log("~~~~~~~~~",c)el.children.forEach(c => {c.path = el.path + '/' + c.pathif (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 function filterDynamicRoutes(routes) {const res = []routes.forEach(route => {if (route.permissions) {if (auth.hasPermiOr(route.permissions)) {res.push(route)}} else if (route.roles) {if (auth.hasRoleOr(route.roles)) {res.push(route)}}})return res
}export const loadView = (view) => {if (process.env.NODE_ENV === 'development') {return (resolve) => require([`@/views/${view}`], resolve)} else {// 使用 import 实现生产环境的路由懒加载return () => import(`@/views/${view}`)}
}export default permission

相关文章:

实战指南:Vue 2基座 + Vue 3 + Vite + TypeScript微前端架构实现动态菜单与登录共享

实战指南&#xff1a;Vue 2基座 Vue 3 Vite TypeScript子应用vue2微前端架构实现动态菜单与登录共享 导读&#xff1a; 在当今的前端开发中&#xff0c;微前端架构已经成为了一种流行的架构模式。本文将介绍如何结合Vue 2基座、Vue 3子应用、Vite构建工具和TypeScript语言…...

Java面试进阶指南:高级知识点问答精粹(一)

Java 面试问题及答案 1. 什么是Java中的集合框架&#xff1f;它包含哪些主要接口&#xff1f; 答案&#xff1a; Java集合框架是一个设计用来存储和操作大量数据的统一的架构。它提供了一套标准的接口和类&#xff0c;使得我们可以以一种统一的方式来处理数据集合。集合框架主…...

儿童礼物笔记

文章目录 女孩礼物毛绒玩具音乐水晶系列水彩笔 男孩礼物益智类玩具积木类泡沫类机动玩具类 小孩过生日或儿童节&#xff0c;选礼物想破脑袋&#xff0c;做个笔记吧。 如果自家的小孩&#xff0c;还好说些&#xff0c;送亲友就需要动动脑筋。 女孩礼物 毛绒玩具 不错的选择&a…...

LeetCode215数组中第K个最大元素

题目描述 给定整数数组 nums 和整数 k&#xff0c;请返回数组中第 k 个最大的元素。请注意&#xff0c;你需要找的是数组排序后的第 k 个最大的元素&#xff0c;而不是第 k 个不同的元素。你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。 解析 快速排序的思想&#xff…...

LeetCode //C - 143. Reorder List

143. Reorder List You are given the head of a singly linked-list. The list can be represented as: L0 → L1 → … → Ln - 1 → Ln Reorder the list to be on the following form: L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → … You may not modify the values i…...

速盾:cdn如何解析?

CDN是内容分发网络&#xff08;Content Delivery Network&#xff09;的缩写&#xff0c;它是一种通过在全球范围内分布节点服务器来提供高性能、高可用性的网络服务的技术。CDN的主要功能是通过将内容分发到离用户更近的服务器节点&#xff0c;从而加速用户对网站、应用程序、…...

K8s集群调度续章

目录 一、污点&#xff08;Taint&#xff09; 1、污点&#xff08;Taint&#xff09; 2、污点组成格式 3、当前taint effect支持如下三个选项&#xff1a; 4、查看node节点上的污点 5、设置污点 6、清除污点 7、示例一 查看pod状态&#xff0c;模拟驱逐node02上的pod …...

大工作量LUAD代谢重编程模型多组学(J Transl Med)

目录 1&#xff0c;单细胞早期、晚期和转移性 LUAD 的细胞动力学变化 2&#xff0c;细胞代谢重编程介导的LUAD驱动恶性转移的异质性 3&#xff0c;模型构建 S-MMR评分管线构建 4&#xff0c;S-MMR 模型的预后评估 5&#xff0c; 还开发了S-MMR 评分网络工具 6&#xff0c…...

C语言#include<>和#include““有什么区别?

一、问题 有两种头⽂件包含的形式&#xff0c;⼀种是⽤尖括号将头⽂件括起&#xff0c;⼀种是⽤双引号将⽂件括起。那么&#xff0c;这两种形式有什么区别呢&#xff1f; 二、解答 这两种包含头⽂件的形式都是合法的&#xff0c;也是经常在代码中看到的&#xff0c;两者的区别…...

正在直播:Microsoft Copilot Studio 新增支持Copilot代理、Copilot扩展等多项功能

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…...

数据通信基本概念汇总

1. 数据通信基础 网关: 提供协议转换&#xff0c;路由选择&#xff0c;数据交换的网络设备 报文: 网络中所传递的一个数据单元。 数据载荷: 最终要传递的信息 封装: 给数据载荷添加头部和尾部的过程(形成新的报文) 解封装: 给数据载荷去掉头部和尾部的过程(获取数据载荷) 终端设…...

AcWing 835. Trie字符串统计——算法基础课题解

AcWing 835. Trie 字符串统计 题目描述 维护一个字符串集合&#xff0c;支持两种操作&#xff1a; I x 向集合中插入一个字符串 &#x1d465;&#xff1b;Q x 询问一个字符串在集合中出现了多少次。 共有 &#x1d441; 个操作&#xff0c;所有输入的字符串总长度不超过 1…...

RT-DETR算法改进【NO.1】借鉴CVPR2024中的StarNet网络StarBlock改进算法

前 言 YOLO算法改进的路有点拥挤,尝试选择其他的baseline作为算法研究,可能会更加好发一些文章。后面将陆续介绍RT-DETR算法改进的方法思路。 很多朋友问改进如何选择是最佳的,下面我就根据个人多年的写作发文章以及指导发文章的经验来看,按照优先顺序进行排序讲解…...

5,串口编程---实现简单的用串口发送接收数据

单片机通过串口向PC机发送数据 PC机通过串口接收单片机发过来的数据 1.UART和USART的区别&#xff1a; USART支持同步通信方式,可以通过外部时钟信号进行同步传输,而UART仅支持异步通信方式 本开发板STM32F103ZET6有5个串口&#xff0c;用串口1作调试串口&#xff0c;因为串…...

LeetCode583:两个字符串的删除操作

题目描述 给定两个单词 word1 和 word2 &#xff0c;返回使得 word1 和 word2 相同所需的最小步数。 每步 可以删除任意一个字符串中的一个字符。 代码 解法1 /*dp[i][j]&#xff1a;以i-1为结尾的wrod1中有以j-1为尾的word2的个数为了让word1和word2相同&#xff0c;最少操作…...

LLama学习记录

学习前&#xff1a; 五大问题&#xff1a; 为什么SwiGLU激活函数能够提升模型性能&#xff1f;RoPE位置编码是什么&#xff1f;怎么用的&#xff1f;还有哪些位置编码方式&#xff1f;GQA&#xff08;Grouped-Query Attention, GQA&#xff09;分组查询注意力机制是什么&…...

如何克隆非默认分支

直接git clone下来的我们知道是默认分支&#xff0c;那如何克隆其他分支呢&#xff1a; 比如这个&#xff0c;我们想克隆AdvNet。 我们可以在本地文件夹打开Git Bash 依次输入&#xff1a; git clone --branch AdvNet https://github.com/wgcban/SemiCD.git cd SemiCD git b…...

数据结构——图

一 图论基本概念 Directed Acyclic Graph &#xff08;DAG&#xff09; 二 图的存储 ①邻接矩阵(适用于稠密图) ②邻接表(适用于稀疏图) 三、图的遍历 ①深度优先搜索 //(基于邻接表实现&#xff0c;以有向图为例) //DFS:Depth First Search 深度优先搜索 //1、访问起始顶点 …...

蓝桥杯—SysTick中断精准定时实现闪烁灯

在嵌入式系统中&#xff0c;SysTick_Handler 是一个中断服务例程&#xff08;Interrupt Service Routine, ISR&#xff09;&#xff0c;用于处理 SysTick 定时器的中断。SysTick 定时器通常用于提供一个周期性的定时中断&#xff0c;可以用来实现延时或者周期性任务。 SysTick…...

ML307R OpenCPU UDP使用

一、UDP通信流程 二、示例 三、UDP通信代码 一、UDP通信流程 ML307R UDP 是使用LWIP的标准的通信,具体UDP流程可以自行百度 二、示例 实验目的:实现把接收的数据再发送到服务端 测试网址:UDP电脑端测试网址 因为是4G,所以必须用外网的 /* 测试前请先补充如下参数 */…...

pod详解

目录 pod pod基本介绍 k8s集群中pod两种使用方式 pause容器使得Pod中所有容器共享两种资源&#xff1a;网络和存储 kubernetes中的pause容器主要为每个容器提供以下功能 k8s设计这样的pod概念和特殊组成结构有什么用意 pod分类 pod容器的分类 基础容器&#xff08;infr…...

免费插件集-illustrator插件-Ai插件-文本对象分行

文章目录 1.介绍2.安装3.通过窗口>扩展>知了插件4.功能解释5.总结 1.介绍 本文介绍一款免费插件&#xff0c;加强illustrator使用人员工作效率&#xff0c;进行文本对象分行。首先从下载网址下载这款插件 https://download.csdn.net/download/m0_67316550/87890501&…...

web学习笔记(五十九)

目录 1.style样式 1.1作用域 scoped 1.2 less和 sass 1.3 less和 sass两者的区别 2. 计算属性computed 3. 响应式基础reactive() 4. 什么是MVVM? 1.style样式 1.1作用域 scoped scoped表示样式作用域&#xff0c;把内部的样式仅限于当前组件模板生效&#xff0c;其…...

UE5 UE4 快速定位节点位置

在材质面板中&#xff0c;找到之前写的一个节点&#xff0c;想要修改&#xff0c;但是当时写的比较多&#xff0c;想要快速定位到节点位置. 在面板下方的 Find Results面板中&#xff0c;输入所需节点&#xff0c;找结果后双击&#xff0c;就定位到该节点处。 同理&#xff0c;…...

go routing 之 gorilla/mux

1. 背景 继续学习 go 2. 关于 routing 的学习 上一篇 go 用的库是&#xff1a;net/http &#xff0c;这次我们使用官方的库 github.com/gorilla/mux 来实现 routing。 3. demo示例 package mainimport ("fmt""net/http""github.com/gorilla/mux&…...

新火种AI|警钟长鸣!教唆自杀,威胁人类,破坏生态,AI的“反攻”值得深思...

作者&#xff1a;小岩 编辑&#xff1a;彩云 在昨天的文章中&#xff0c;我们提到了谷歌的AI Overview竟然教唆情绪低迷的网友“从金门大桥跳下去”。很多人觉得&#xff0c;这只是AI 模型的一次错误判断&#xff0c;不会有人真的会因此而照做。但现实就是比小说电影中的桥段…...

AAA实验配置

一、实验目的 掌握AAA本地认证的配置方法 掌握AAA本地授权的配置方法 掌握AAA维护的方法 1.搭建实验拓扑图 2.完成基础配置&#xff1a; 3.使用ping命令测试两台设备的连通性&#xff1a; 二、配置AAA 1.打开R1&#xff1a;配置AAA方案 这两个方框内的可以改名&#xff0c…...

Maven高级详解

文章目录 一、分模块开发与设计分模块开发的意义模块拆分原则 分模块开发(模块拆分)创建Maven模块书写模块代码通过maven指令安装模块到本地仓库(install指令) 二、依赖管理依赖传递可选依赖排除依赖可选依赖和排除依赖的区别 三、聚合与继承聚合工程聚合工程开发创建Maven模块…...

C++的算法:模拟算法

模拟算法是一种基于事物运动变化过程的模型,通过计算机程序来模拟实际系统行为或过程的方法。在C++中,模拟算法常用于解决复杂系统或过程的建模与仿真问题。本文将介绍模拟算法的实现思路及实际应用,并通过具体的实例来展示如何在C++中实现模拟算法。 一、模拟算法的实现思…...

Spring boot集成easy excel

Spring boot集成easy excel 一 查看官网 easyexcel官方网站地址为easyexcel官网&#xff0c;官网的信息比较齐全&#xff0c;可以查看官网使用easyexcel的功能。 二 引入依赖 使用easyexcel&#xff0c;首先要引入easyexcel的maven依赖&#xff0c;具体的版本根据你的需求去…...