实战指南: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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于Docker的ROS开发

本文主要介绍如何使用Docker在Windows和Linux环境中部署并使用ROS&#xff0c;通过Docker Container运行ROS&#xff0c;可以方便我们在一个本地环境中运行多个ROS版本。 更多内容&#xff0c;访问专栏目录获取实时更新。 关于ROS的版本 参考ROS1 Distribution Wiki和ROS2 Dis…

nginx源码阅读理解 [持续更新,建议关注]

文章目录 前述一、nginx 进程模型基本流程二、源码里的小点1.对字符串操作都进行了原生实现2.配置文件解析也是原生实现待续 前述 通过对 nginx 的了解和代码简单阅读&#xff0c;发现这个C代码的中间件确实存在过人之处&#xff0c;使用场景特别多&#xff0c;插件模块很丰富…

10款AI工具,让工作生活学习更高效

我看大家都推荐的差不多了&#xff0c;常见好用的PC软件就那些&#xff0c;我不想反复“咀嚼”了&#xff0c;我想另辟蹊径推荐点不一样的&#xff0c;比如10款PC端的AI网站。AI已经全方位“侵入”我们的生活&#xff0c;从AI写作到AI绘画&#xff0c;从AI视频到AI语音&#xf…

Thingsboard规则链:Switch节点详解

在物联网&#xff08;IoT&#xff09;领域&#xff0c;数据的高效处理与自动化决策是构建智能系统的核心。作为一款强大的物联网平台&#xff0c;Thingsboard通过其规则引擎为开发者提供了高度灵活的工具&#xff0c;其中Switch节点是实现消息条件路由的关键组件。本文将全方位…

【深度学习】Transformer梳理

零、前言 对于transformer&#xff0c;网上的教程使用记号、术语不一 。 最关键的一点&#xff0c;网上各种图的简化程度不一 &#xff08;画个图怎么能这么偷懒&#xff09; &#xff0c;所以我打算自己手画一次图。 看到的最和善&#xff08;但是不是那么靠谱&#xff0c;我…

黑龙江某市数字孪生地下水监测系统平台项目建设经验

项目背景 地下水是一种特殊而珍贵的资源&#xff0c;它具有不可替代性&#xff0c;与经济发展及人民生活息息相关&#xff0c;针对日趋严峻的水资源危机&#xff0c;如何合理利用有限的水资源&#xff0c;保障国民经济的可持续发展是一个迫切需要解决的问题。 黑龙江某市积极…

eclipse启动时间过长的问题

项目场景&#xff1a; 由于我用eclipse比较习惯&#xff0c;虽然IDEA很好&#xff0c;但是因为收费&#xff0c;所以在个人开发学习过程中一直还是使用eclipse&#xff0c;本文不讨论eclipse与IDEA孰优孰劣问题。 开发环境&#xff1a; 操作系统&#xff1a;Windows 11 22631…

【小呆的力学笔记】连续介质力学的知识点回顾一:运动和变形

文章目录 1. 运动的描述2. 拉格朗日描述下的变形2.1 线元的变化2.2 体元的变化2.3 面元的变化 1. 运动的描述 在连续介质力学中&#xff0c;存在着两种对运动的描述&#xff0c;一种为拉格朗日描述&#xff0c;即通过描述每个物质点的运动来描述整个变形体的运动&#xff0c;也…

如何将音频中的人声分离出来?

想要把一段视频中的人声跟背景音乐分离开来&#xff0c;找个好一点的音频处理软件就能把声音分离了&#xff0c;常见的有以下方法&#xff0c;一起来看看吧。 pr 打开软件&#xff0c;然后将电脑上的音频文件&#xff0c;上传到软件中&#xff0c;然后按住[ctrla]选择所有音频…

【数据结构】 排序算法 ~ 总结篇

文章目录 1. 排序几个重点概念的理解2. 排序算法的分析&#x1f427; 1. 排序几个重点概念的理解 2. 排序算法的分析&#x1f427;

Git学习篇

目录 使用命令导入项目 使用命令导入项目 1. 使用git init 命令初始化一个新的Git仓库。 git init 是 Git 命令&#xff0c;用于初始化一个新的 Git 仓库。当您想要开始跟踪一个新项目的版本控制时&#xff0c;可以运行 git init 命令来初始化一个空的 Git 仓库。 如果出现以下…

6-继承

6-继承 1、基本语法和方式2、继承的基本特点2.1 三种继承方式相同的基本点2.2 三种继承方式的差别2.3 公有继承的独有特点 3、子类的构造、析构3.1 子类的构造3.2 子类的析构3.3 子类的拷贝构造函数3.4 子类的拷贝赋值 4、多重继承4.1 内存布局4.2 类型转换4.3 名字冲突问题 5、…

10. C++异步IO处理库和使用libevent实现高性能服务器

C比较有名的异步IO处理库 libevent 这个主要使用的是epoll。libevthplibuvlibev 我们主要介绍libevent。 libevent重要函数 event_base_new 这个可以对应于epoll_create也就是创建一个实例。还可以初始化libevent所有管理相关的代码。比如说所能用到的队列&#xff0c;栈&a…

Linux内核编译流程3.10

一、内核源代码编译流程 编译环境&#xff1a; cat /etc/redhat-release CentOS Linux release 7.4.1708 (Core) Linux内核版本&#xff1a; uname -r 3.10.0-693.el7.x86_64 编译内核源代码版本&#xff1a;linux-4.19.90-all-arch-master cp /boot/config-xxx到内核源…

数据库(9)——DQL基础查询

数据查询 数据查询是SQL中最复杂的&#xff0c;语法结构为 SELECT 字段列表 FROM 表名列表 WHERE 条件列表 GROUP BY 分组字段列表 HAVING 分组后字段列表 ORDER BY 排序字段列表 LIMIT 分页参数 查询多个字段 SELECT 字段1&#xff0c;字段2...FROM…

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

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

linux开发之设备树基本语法一

设备树的根节点 设备树子节点和子子节点,子节点在根节点范围内 包含子节点以及子子节点 节点名称 比如这里led就是这个gpio的小名,可以直接用 gpio22020101是这里的名字,也就是要用这个gpio,符号后面的一串数字使用了这个gpio的寄存器地址,因为可能会用很多gpio,所以加入寄存…

Pushmall共享分销电商SaaS版2024年 5月模块开发优化完成

Pushmall共享分销电商 2024年 5月模块开发优化完成 1、**实现SaaS框架业务&#xff1a;**多租户、多商家、多门店&#xff0c;及商家入驻、商品管理。 2、租户小程序管理&#xff1a;对租户的小程序业务管理。 3、店铺小程序管理&#xff1a;对租户多店铺小程序绑定。 4、会员分…

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

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

精酿啤酒:品质与口感对啤酒市场价格的影响

啤酒作为一种大众化的产品&#xff0c;其品质与口感对市场价格有着显著的影响。对于Fendi club啤酒而言&#xff0c;其卓着的品质和与众不同的口感又加上市场价格相对实惠&#xff0c;受到消费者的青睐。 品质是决定啤酒市场价格的重要因素。Fendi club啤酒选用天然小麦原料&am…