micro"/>
基于micro
简述
本文是在对之前搭建和学习micro-app的基础上的进一步研究学习。
因为我们目前项目使用的框架是vue-element-admin,所以还需要研究一下micro-app在vue-element-admin的使用方法。
关于micro-app在vue-element-admin的实现,百度什么也没找到,只能按照之前搭建的micro-app的基础模仿着搭建着自己搞。
最终实现效果如下图所示:
接下来开始搭建过程
1、创建项目目录
关于目录的创建,新增文件夹micro-app-element,文件夹中有三个vue-element-admin项目:vue-element-base(基座)、vue-element-first(子应用1)、vue-element-second(子应用2)
代码下载
具体如下图所示:
其中vue-element-base(基座)、vue-element-first(子应用1)、vue-element-second(子应用2)是创建的vue-element-admin项目,如果不会创建,可参考
我这边是之前创建好的现成的包,拿过来直接解压了三个修改了一下名称,一个基座两个个子应用,项目内容大致如下
2、基座配置
我这边按之前搭建micro-app的过程一步步来,只是把之前的创建项目的过程省略掉了,现在直接用现成的vue-element-admin,搭建参考
注:接下来的配置都是在vue-element-base中进行配置的
2-1、配置vue.config.js
之前搭建的时候配置了localhost和port端口,vue-element-admin看情况是封装好的,不用修改太多
2-2、安装micro-app
安装micro-app插件,安装在base目录下
npm install @micro-zoe/micro-app --save
安装完成后,可以在基座package.json中看到安装好的@micro-zoe/micro-app,如下图所示:
2-3、配置micro
创建micro配置文件夹,配置config和index
config.js
/**
* 子应用前缀
*/
export const CHILD_PREFIX = 'child'/**
* 子应用地址
*/
export const MICRO_APPS = [{ name: 'first-child', url: `http://localhost:9528` },{ name: 'second-child', url: `http://localhost:9529` }
]/**
* 全局资源
*/
export const GLOBAL_ASSETS = {js: [],css: []
}
index.js
import microApp from '@micro-zoe/micro-app'
import * as config from './config'/** 启用 micro */
microApp.start({preFetchApps: config.MICRO_APPS,globalAssets: config.GLOBAL_ASSETS
})
2-4、修改main.js
修改main.js。引入micro
import './micro'
2-5、修改AppMain.vue
AppMain.vue修改(这个我在项目里找了一下,找到系统主体部分是写在AppMain.vue中的。按照之前搭建时的思路进行了一些修改)
差不多对AppMain.vue进行了如下图所示修改:
完整代码:
<template><section class="app-main"><transition name="fade-transform" mode="out-in"><keep-alive :include="cachedViews"><!--element-ui视图部分,这边可以判断是不是子应用,如果子应用显示micro-app,否则直接显示router-view--><micro-appv-if="isChild"v-bind="micro"destory/><router-view v-else :key="key" /></keep-alive></transition></section>
</template><script>
import { MICRO_APPS, CHILD_PREFIX } from '../../micro/config'export default {name: 'AppMain',data() {return {isChild: false, /** 是否为子模块 */micro: {url: '', /** 子模块地址 */key: '', /** vue 标签的 key 值,用于不同子模块间的切换时,组件重新渲染 */name: '', /** 子模块名称,唯一 */data: {}, /** 子模块数据 */baseroute: '' /** 子模块数据 */},prefix: CHILD_PREFIX /** 子模块链接前缀 */}},computed: {cachedViews() {return this.$store.state.tagsView.cachedViews},key() {return this.$route.path}},watch: {$route(val) { /** 监听路由变化修改视图显示 */this.changeChild(val)}},created() {this.changeChild(this.$route)},methods: {/*** 获取子模块 url 和 name* */getAppUrl(name) {return MICRO_APPS.find(app => app.name === name) || {}},/*** 修改子视图显示* */changeChild(route) {const path = route.path.toLowerCase()const paths = path.split('/')// 判断是否为子模块,子模块有固定的前缀,在 micro/config 设置this.isChild = paths.length > 2 && paths[1] === CHILD_PREFIXif (this.isChild) {const app = this.getAppUrl(paths[2])this.micro = {...app,data: { name: route.name },key: `${app.name}`,baseroute: `/${CHILD_PREFIX}/${paths[2]}`}}}}
}
</script><style lang="scss" scoped>
.app-main {/* 50= navbar 50 */min-height: calc(100vh - 50px);width: 100%;position: relative;overflow: hidden;
}.fixed-header+.app-main {padding-top: 50px;
}.hasTagsView {.app-main {/* 84 = navbar + tags-view = 50 + 34 */min-height: calc(100vh - 84px);}.fixed-header+.app-main {padding-top: 84px;}
}
</style><style lang="scss">
// fix css style bug in open el-dialog
.el-popup-parent--hidden {.fixed-header {padding-right: 15px;}
}
</style>
2-6、基座中配置子应用1路由
在基座中配置子应用1路由(vue-element-admin中有好多页面,随便找了两个配置子应用路由
vue-element-base\src\router\modules\first-child.js
// 子应用1路由菜单
import Layout from '@/layout'
import { CHILD_PREFIX } from '@/micro/config'const appFirstRouter = {path: `/${CHILD_PREFIX}/first-child`,component: Layout,redirect: `/${CHILD_PREFIX}/first-child`,name: 'FirstChild',meta: {title: '子应用模块',icon: 'nested'},children: [{path: 'menu1',name: 'Menu1',meta: { title: '子应用菜单1' }},{path: 'menu2',name: 'Menu2',meta: { title: '子应用菜单2' }}]
}
export default appFirstRouter
vue-element-base\src\router\modules\index.js
// 1.导入
import appFirstRouter from './modules/first-child'// 2.使用
appFirstRouter,
2-7、基座中配置子应用2路由
在基座中配置子应用2路由(同样vue-element-admin中有好多页面,随便找了两个配置子应用路由,只要不和子应用1重复即可)—这边的创建差不多就是把1复制过来改改
vue-element-base\src\router\modules\second-child.js
// 子应用2路由菜单
import Layout from '@/layout'
import { CHILD_PREFIX } from '@/micro/config'const appSecondRouter = {path: `/${CHILD_PREFIX}/second-child`,component: Layout,redirect: `/${CHILD_PREFIX}/second-child`,name: 'SecondChild',meta: {title: '子应用2模块',icon: 'table'},children: [{path: 'dynamic-table',name: 'DynamicTable',meta: { title: '子应用2菜单1' }},{path: 'drag-table',name: 'DragTable',meta: { title: '子应用2菜单2' }}]
}
export default appSecondRouter
vue-element-base\src\router\modules\index.js
// 1.导入
import appSecondRouter from './modules/second-child'// 2.使用
appSecondRouter,
到此对基座的改造就结束了。
3、子应用1配置
子应用1为vue-element-first。接下来的配置都是在vue-element-first下进行的配置
3-1、修改vue.config.js
修改vue.config.js文件,设置允许跨域
headers: { // 设置本地运行的跨域权限'Access-Control-Allow-Origin': '*'
},
3-2、配置micro
和基座一样,配置micro。新建在src下新建文件夹micro。并在micro下新建index.js文件
// 设置 webpack 的公共路径
if (window.__MICRO_APP_ENVIRONMENT__) {// eslint-disable-next-line__webpack_public_path__ = window.__MICRO_APP_PUBLIC_PATH__ || '/'
}
3-3、修改main.js
main.js
// 引入 publicPath 设置
import './micro'import Vue from 'vue'import Cookies from 'js-cookie'import 'normalize.css/normalize.css' // a modern alternative to CSS resetsimport Element from 'element-ui'
import './styles/element-variables.scss'import '@/styles/index.scss' // global cssimport App from './App'
import store from './store'
import router from './router'import i18n from './lang' // internationalization
import './icons' // icon
import './permission' // permission control
import './utils/error-log' // error logimport * as filters from './filters' // global filters/**
* 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 ! ! !
*/
if (process.env.NODE_ENV === 'production') {const { mockXHR } = require('../mock')mockXHR()
}Vue.use(Element, {size: Cookies.get('size') || 'medium', // set element-ui default sizei18n: (key, value) => i18n.t(key, value)
})// register global utility filters
Object.keys(filters).forEach(key => {Vue.filter(key, filters[key])
})Vue.config.productionTip = false// new Vue({
// el: '#app',
// router,
// store,
// i18n,
// render: h => h(App)
// })
let app/**
* 挂载函数
*/
function mount() {app = new Vue({el: '#app',router,store,i18n,render: function(h) { return h(App) }})
}/**
* 卸载函数
*/
function unmount() {app.$destroy()app.$el.innerHTML = ''app = null
}/** 微前端环境下,注册mount和unmount方法 */
if (window.__MICRO_APP_ENVIRONMENT__) { window[`micro-app-${window.__MICRO_APP_NAME__}`] = { mount, unmount } } else { mount() }
3-4、修改vue文件
3-5、修改路由(子应用1)
/** When your routing table is too long, you can split it into small modules **/import Layout from '@/layout'console.log(' window.__MICRO_APP_BASE_ROUTE__', window.__MICRO_APP_BASE_ROUTE__)
const nestedRouter = {path: window.__MICRO_APP_BASE_ROUTE__ || '/nested',component: Layout,// redirect: '/nested/menu1/menu1-1',name: 'Nested',meta: {title: '子应用',icon: 'nested'},children: [{path: 'menu1',component: () => import('@/views/nested/menu1/index'), // Parent router-viewname: 'Menu1',meta: { title: '子应用菜单11' }},{path: 'menu2',name: 'Menu2',component: () => import('@/views/nested/menu2/index'),meta: { title: '子应用菜单22' }}]
}export default nestedRouter
修改完成后运行子应用1和基座,在基座中看到的子应用1效果如下图所示:
npm run dev
3-6、问题解决
原以为这种是路由配置有问题,但是改来改去要么404要么嵌套。不管改基座还是子应用。都没有任何效果。后面看到了一个react的文章。虽然语法等不一样,但是看到了和我上图一样的嵌套bug。
上图中子应用全部嵌套在了基座里,包含了基座已有的菜单栏及头部,所以要找到子应用的layout进行判断,如果是基座环境,则只显示主体内容。否则(即子应用)则可以全部显示。具体修改如下所示:
修改完成后不在有嵌套,但是在基座中点击子应用菜单,只有上方头部菜单栏变化,中间主要部分内容没有变化。要在子应用layout中对数据做一个监听。
micro-app-element\vue-element-first\src\layout\index.vue完整代码:
<template><app-main v-if="inBase" /><div v-else :class="classObj" class="app-wrapper"><div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside" /><sidebar class="sidebar-container" /><div :class="{hasTagsView:needTagsView}" class="main-container"><div :class="{'fixed-header':fixedHeader}"><navbar /><tags-view v-if="needTagsView" /></div><app-main /><right-panel v-if="showSettings"><settings /></right-panel></div></div>
</template><script>
import RightPanel from '@/components/RightPanel'
import { AppMain, Navbar, Settings, Sidebar, TagsView } from './components'
import ResizeMixin from './mixin/ResizeHandler'
import { mapState } from 'vuex'export default {name: 'Layout',components: {AppMain,Navbar,RightPanel,Settings,Sidebar,TagsView},mixins: [ResizeMixin],computed: {...mapState({sidebar: state => state.app.sidebar,device: state => state.app.device,showSettings: state => state.settings.showSettings,needTagsView: state => state.settings.tagsView,fixedHeader: state => state.settings.fixedHeader}),classObj() {return {hideSidebar: !this.sidebar.opened,openSidebar: this.sidebar.opened,withoutAnimation: this.sidebar.withoutAnimation,mobile: this.device === 'mobile'}},inBase() { // 判断是不是基座console.log('inbase microApp', window.microApp)console.log('inbase', window.__MICRO_APP_BASE_APPLICATION__)return !!window.microApp}},created() {/** 绑定数据【data属性】监听事件 */window.microApp && window.microApp.addDataListener(this.dataListener)},methods: {handleClickOutside() {this.$store.dispatch('app/closeSideBar', { withoutAnimation: false })},/** 监听路径,在基座中点击子应用菜单时,菜单正常切换,但主体部分内容不变 */dataListener(data) {if (data.name !== this.$route.name) {this.$router.push({ name: data.name })}}}
}
</script><style lang="scss" scoped>@import "~@/styles/mixin.scss";@import "~@/styles/variables.scss";.app-wrapper {@include clearfix;position: relative;height: 100%;width: 100%;&.mobile.openSidebar {position: fixed;top: 0;}}.drawer-bg {background: #000;opacity: 0.3;width: 100%;top: 0;height: 100%;position: absolute;z-index: 999;}.fixed-header {position: fixed;top: 0;right: 0;z-index: 9;width: calc(100% - #{$sideBarWidth});transition: width 0.28s;}.hideSidebar .fixed-header {width: calc(100% - 54px)}.mobile .fixed-header {width: 100%;}
</style>
修改完成后,micro-app在vue-element-admin中的效果就算实现了。效果图如下:
4、子应用2配置
子应用2配置(基本和子应用1配置一致,只需微改即可)
子应用2为vue-element-second。接下来的配置都是在vue-element- second下进行的配置。
4-1、修改vue.config.js
修改vue.config.js文件,设置允许跨域
headers: { // 设置本地运行的跨域权限'Access-Control-Allow-Origin': '*'
},
4-2、配置micro
和基座一样,配置micro。新建在src下新建文件夹micro。并在micro下新建index.js文件
// 设置 webpack 的公共路径
if (window.__MICRO_APP_ENVIRONMENT__) {// eslint-disable-next-line__webpack_public_path__ = window.__MICRO_APP_PUBLIC_PATH__ || '/'
}
4-3、修改main.js
main.js
// 引入 publicPath 设置
import './micro'
import Vue from 'vue'import Cookies from 'js-cookie'import 'normalize.css/normalize.css' // a modern alternative to CSS resetsimport Element from 'element-ui'
import './styles/element-variables.scss'import '@/styles/index.scss' // global cssimport App from './App'
import store from './store'
import router from './router'import i18n from './lang' // internationalization
import './icons' // icon
import './permission' // permission control
import './utils/error-log' // error logimport * as filters from './filters' // global filters/**
* 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 ! ! !
*/
if (process.env.NODE_ENV === 'production') {const { mockXHR } = require('../mock')mockXHR()
}Vue.use(Element, {size: Cookies.get('size') || 'medium', // set element-ui default sizei18n: (key, value) => i18n.t(key, value)
})// register global utility filters
Object.keys(filters).forEach(key => {Vue.filter(key, filters[key])
})Vue.config.productionTip = false// new Vue({
// el: '#app',
// router,
// store,
// i18n,
// render: h => h(App)
// })
let app/**
* 挂载函数
*/
function mount() {app = new Vue({el: '#app',router,store,i18n,render: function(h) { return h(App) }})
}/**
* 卸载函数
*/
function unmount() {app.$destroy()app.$el.innerHTML = ''app = null
}/** 微前端环境下,注册mount和unmount方法 */
if (window.__MICRO_APP_ENVIRONMENT__) {window[`micro-app-${window.__MICRO_APP_NAME__}`] = { mount, unmount }
} else {mount()
}
4-4、修改vue文件
4-5、修改路由(子应用2)
/** When your routing table is too long, you can split it into small modules **/import Layout from '@/layout'const tableRouter = {path: window.__MICRO_APP_BASE_ROUTE__ || '/table',component: Layout,// redirect: '/table/complex-table',name: 'Table',meta: {title: '子应用2',icon: 'table'},children: [{path: 'dynamic-table',component: () => import('@/views/table/dynamic-table/index'),name: 'DynamicTable',meta: { title: '子应用2菜单111' }},{path: 'drag-table',component: () => import('@/views/table/drag-table'),name: 'DragTable',meta: { title: '子应用2菜单222' }},{path: 'inline-edit-table',component: () => import('@/views/table/inline-edit-table'),name: 'InlineEditTable',meta: { title: 'inlineEditTable' }},{path: 'complex-table',component: () => import('@/views/table/complex-table'),name: 'ComplexTable',meta: { title: 'complexTable' }}]
}
export default tableRouter
接下来浏览器效果和搭建子应用1时一样,是嵌套的,如下:
4-6、问题解决
需要对子应用的layout进行判断,如果是基座环境,则只显示主体内容。否则(即子应用)则可以全部显示。至于为什么修改,看子应用1即可,不多说了具体修改如下所示:
micro-app-element\vue-element-second\src\layout\index.vue完整代码:
<template><app-main v-if="inBase" /><div v-else :class="classObj" class="app-wrapper"><div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside" /><sidebar class="sidebar-container" /><div :class="{hasTagsView:needTagsView}" class="main-container"><div :class="{'fixed-header':fixedHeader}"><navbar /><tags-view v-if="needTagsView" /></div><app-main /><right-panel v-if="showSettings"><settings /></right-panel></div></div>
</template><script>
import RightPanel from '@/components/RightPanel'
import { AppMain, Navbar, Settings, Sidebar, TagsView } from './components'
import ResizeMixin from './mixin/ResizeHandler'
import { mapState } from 'vuex'export default {name: 'Layout',components: {AppMain,Navbar,RightPanel,Settings,Sidebar,TagsView},mixins: [ResizeMixin],computed: {...mapState({sidebar: state => state.app.sidebar,device: state => state.app.device,showSettings: state => state.settings.showSettings,needTagsView: state => state.settings.tagsView,fixedHeader: state => state.settings.fixedHeader}),classObj() {return {hideSidebar: !this.sidebar.opened,openSidebar: this.sidebar.opened,withoutAnimation: this.sidebar.withoutAnimation,mobile: this.device === 'mobile'}},inBase() { // 判断是不是基座// console.log('inbase microApp', window.microApp)// console.log('inbase', window.__MICRO_APP_BASE_APPLICATION__)return !!window.microApp}},created() {/** 绑定数据【data属性】监听事件 */window.microApp && window.microApp.addDataListener(this.dataListener)},methods: {handleClickOutside() {this.$store.dispatch('app/closeSideBar', { withoutAnimation: false })},/** 监听路径,在基座中点击子应用菜单时,菜单正常切换,但主体部分内容不变 */dataListener(data) {if (data.name !== this.$route.name) {this.$router.push({ name: data.name })}}}
}
</script><style lang="scss" scoped>@import "~@/styles/mixin.scss";@import "~@/styles/variables.scss";.app-wrapper {@include clearfix;position: relative;height: 100%;width: 100%;&.mobile.openSidebar {position: fixed;top: 0;}}.drawer-bg {background: #000;opacity: 0.3;width: 100%;top: 0;height: 100%;position: absolute;z-index: 999;}.fixed-header {position: fixed;top: 0;right: 0;z-index: 9;width: calc(100% - #{$sideBarWidth});transition: width 0.28s;}.hideSidebar .fixed-header {width: calc(100% - 54px)}.mobile .fixed-header {width: 100%;}
</style>
修改完成后,浏览器实现效果图如下:
到此对于micro-app在vue-element-admin中的使用就介绍完了。
5、打包
关于打包vue-element-admin貌似已经配置好了,直接打包即可
npm run build:prod
打包结果又是这样的,跟之前一样报错:
本地运行不起来,按照网上找的方法修改都没用。所以后面直接部署了,部署下来没有问题。所以打包这边注意以下两点就好了。
1、vue.config.js中的publicPath要修改。基座和子应用都要修改。如下图所示:
2、修改router/index.js。基座和子应用都要修改。修改路由方式。如下图所示:
打包结束后对应基座和子应用下都会生成对应的dist文件夹,如下图所示。单独打包单独部署就是基座和子应用都要打包。
6、部署
使用nginx部署,之前已经研究过了,部署这边就很快。如果不懂可参考部署相关内容。
6-1、dist放置
将打包好的dist文件分别放入对应好的文件夹中,当然不一定像我这样,只要后期目录地址写对即可
6-2、配置
主要是对nginx.conf的配置
主要配置代码:
server {listen 9527;server_name localhost;#charset koi8-r;#access_log logs/host.access.log main;location / {root base/dist;add_header Access-Control-Allow-Origin *;if ( $request_uri ~* ^.+.(js|css|jpg|png|gif|tif|dpg|jpeg|eot|svg|ttf|woff|json|mp4|rmvb|rm|wmv|avi|3gp)$ ){add_header Cache-Control max-age=7776000;add_header Access-Control-Allow-Origin *;}try_files $uri $uri/ /index.html;} }server {listen 9528;server_name localhost;location / {root first-child/dist;add_header Access-Control-Allow-Origin *;if ( $request_uri ~* ^.+.(js|css|jpg|png|gif|tif|dpg|jpeg|eot|svg|ttf|woff|json|mp4|rmvb|rm|wmv|avi|3gp)$ ){add_header Cache-Control max-age=7776000;add_header Access-Control-Allow-Origin *;}try_files $uri $uri/ /index.html;}}server {listen 9529;server_name localhost;location / {root second-child/dist;add_header Access-Control-Allow-Origin *;if ( $request_uri ~* ^.+.(js|css|jpg|png|gif|tif|dpg|jpeg|eot|svg|ttf|woff|json|mp4|rmvb|rm|wmv|avi|3gp)$ ){add_header Cache-Control max-age=7776000;add_header Access-Control-Allow-Origin *;}try_files $uri $uri/ /index.html;}}
6-3、浏览器地址
部署完之后可以在浏览器中输入:
http://127.0.0.1:9527/ // 基座地址
http://127.0.0.1:9528/ // 子应用1地址
http://127.0.0.1:9529/ // 子应用2地址
6-4、部署效果
部署后浏览器显示效果:
总结
到此micro-app在vue-element-admin中的使用框架就算搭完了,关于micro-app在vue-element-admin中的其他使用,还需后续在研究。
更多推荐
基于micro
发布评论