前言:
本篇文章介绍如何将以vue2
为基座,使用qiankun
框架独立技术栈,可复用性进行vue3+vite+ts+arcoDesign
的子项目改造并接入,子应用可根据基座传递过来基础路由进行改变,保证主子应用访问不会报404错误。
整体结构:
vue2+ruoyi+webpack
作为基座,vue+vite+ts+ArcoDesignUI
作为子应用接入,尽量把基座保持整洁,保证基座不保函业务,只分享token及动态菜单。
接入后的效果:
页面响应式还没做好,整体效果可以显现,我将vue2
版本的左侧菜单改成了acro.design
版本的左侧菜单样式,样式尽量保持一致!
vue2基座整改,注册子应用,配置子应用路由
- 安装qiankun
npm i qiankun -S
- 设置需要将子应用的页面嵌入到主应用的某个div中(子应用在主应用上的渲染出口)
<div id="subapp-container">
在主应用(基座)中注册子应用
- 引入
registerMicroApps, start, setDefaultMountApp
import { registerMicroApps, start, setDefaultMountApp } from 'qiankun';
- 注册子应用
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.css';
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 {userApi} from "hskCommApi"
// console.log("asdasd",userApi.login)
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()
/**
* 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
})
// 1. 注册微应用
registerMicroApps([
{
name: 'vinsion-module', // 微应用package.json的name字段
entry: '//192.168.80.15:8010/vinsion-module/', // 微应用访问地址,默认加载这个html页面并解析其中的js动态执行
container: '#subapp-container', // 子应用渲染的出口
// return location.pathname.includes('/vite-vue3-app2')
activeRule: '/hsk-admin/vinsion-module',// 激活路径,微应用路由
sandbox: {
// strictStyleIsolation: true, // 开启样式隔离
},
props: { sharedStore: store, baseName: '/hsk-admin/system-module' }
},
])
// 判断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 = false
new Vue({
el: '#app',
router,
store,
render: h => h(App)
})
- 配置子应用的路由,根据activeRule进行判断当激活的子应用
{
path: '/vinsion-module',
component: Layout,
name: 'vinsion-module',
meta: { title: '生产单位', icon: 'tool' },
children: [
{
path: 'visualization/multi-dimension-data-analysis',
name: 'MultiDimensionDataAnalysis',
meta: { title: '生产单位', noCache: true, icon: 'tool' }
},
]
},
这样基座就算改造完成啦
vue3+vite+ts+acro.Design进行子应用改造
因为qiankun
目前不支持vite
项目,vite
项目需要安装社区依赖vite-plugin-qiankun
- 安装
vite
的qiankun1
社区依赖vite-plugin-qiankun
npm install vite-plugin-qiankun
2. 修改package.json
文件,和主应用注册子应用时的name
值相同
3. 修改vite.config.dev.ts
或者vite.config.ts
文件
import { mergeConfig } from 'vite';
import { fileURLToPath, URL } from 'node:url'
import eslint from 'vite-plugin-eslint';
import qiankun from 'vite-plugin-qiankun';
// import vue from "@vitejs/plugin-vue";
import baseConfig from './vite.config.base';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const packName = require('../package.json').name
export default mergeConfig(
{
base: "/vinsion-module", // 子应用基础地址
mode: 'development',
server: {
open: true,
cors: true,
host: '0.0.0.0',
fs: {
strict: true,
},
https: false,
port:"8010",
headers: {
"Access-Control-Allow-Origin": "*", // 避免跨域
},
},
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
plugins: [
// vue(),
qiankun(`vinsion-module`,{ // 子应用的name值
useDevMode: true
}),
eslint({
cache: false,
include: ['src/**/*.ts', 'src/**/*.tsx', 'src/**/*.vue'],
exclude: ['node_modules'],
}),
],
},
baseConfig
);
- 添加
public-path.ts
,main.ts
引入
public-path.ts
的作用:public-path.ts
文件的作用就是用来设置子应用的静态资源文件的基础路径。在这个文件中,通过判断全局变量__POWERED_BY_QIANKUN__
是否存在,来确定当前应用是否运行在qiankun
的微前端环境中。如果是,则通过__INJECTED_PUBLIC_PATH_BY_QIANKUN__
这个全局变量获取qiankun
注入的基础路径,然后使用__webpack_public_path__
动态设置静态资源文件的基础路径。
代码:/* eslint-disable camelcase */ /* eslint-disable no-underscore-dangle */ // src/public-path.ts if ((window as any).__POWERED_BY_QIANKUN__) { const __webpack_public_path__ = (window as any).__INJECTED_PUBLIC_PATH_BY_QIANKUN__; }
main.ts
引入:
import './public-path'
- 改造
main.ts
文件
/* eslint-disable no-underscore-dangle */
import './public-path'
import { createApp } from 'vue';
import ArcoVue from '@arco-design/web-vue';
import ArcoVueIcon from '@arco-design/web-vue/es/icon';
import globalComponents from '@/components';
import { renderWithQiankun, qiankunWindow } from 'vite-plugin-qiankun/dist/helper';
import router from './router';
import store from './store';
import i18n from './locale';
import directive from './directive';
import './mock';
import App from './App.vue';
import '@/assets/style/global.less';
import '@/api/interceptor';
// 第五版本
let app: any;
// 判断是否在qiankun环境下
if (!qiankunWindow.__POWERED_BY_QIANKUN__) {
app = createApp(App);
app.use(ArcoVue, {});
app.use(ArcoVueIcon);
app.use(router);
app.use(store);
app.use(i18n);
app.use(globalComponents);
app.use(directive);
app.mount('#app');
} else {
renderWithQiankun({
// qiankun的生命周期,挂载
mount(props) {
app = createApp(App);
// 传递的值可以获取到了
app.use(ArcoVue, {});
app.use(ArcoVueIcon);
app.use(router);
app.use(store);
app.use(i18n);
app.use(globalComponents);
app.use(directive);
app.mount(
props.container
? props.container.querySelector("#app")
: document.getElementById("app")
);
},
// 应用加载
bootstrap() {
console.log("--bootstrap");
},
// 修改
update(props) {
console.log("--update", props);
},
// 销毁
unmount() {
console.log("--unmount");
app?.unmount();
},
});
}
router/index.ts
文件修改,注意 一定要是history
模式,vue3
只能用createWebHistory
,根据qiankun
环境来使用路由路径
/* eslint-disable no-underscore-dangle */
import { createRouter, createWebHistory } from 'vue-router';
import NProgress from 'nprogress'; // progress bar
import { qiankunWindow } from 'vite-plugin-qiankun/dist/helper'
import 'nprogress/nprogress.css';
import { appRoutes } from './routes';
import { REDIRECT_MAIN, NOT_FOUND_ROUTE } from './routes/base';
import createRouteGuard from './guard';
NProgress.configure({ showSpinner: false }); // NProgress Configuration
console.log("router/index.ts",qiankunWindow.__POWERED_BY_QIANKUN__)
const router = createRouter({
history: createWebHistory(qiankunWindow.__POWERED_BY_QIANKUN__ ? '/hsk-admin/vinsion-module' :'/vinsion-module'),
routes: [
{
path: '/',
redirect: 'login',
},
{
path: '/login',
name: 'login',
component: () => import('@/views/login/index.vue'),
meta: {
requiresAuth: false,
},
},
...appRoutes,
REDIRECT_MAIN,
NOT_FOUND_ROUTE,
],
scrollBehavior() {
return { top: 0 };
},
});
createRouteGuard(router);
export default router;
- 根据当前是否是子应用进行
DOM
的显示和不显示,当把子应用渲染到父组件中的时候,相对的菜单,头部标签栏也会被渲染过去,这时候就需要根据当前是否子应用判断是否要显示菜单,标签栏,头部了,下面是个简单的例子:根据qiankunWindow.__POWERED_BY_QIANKUN__
进行判断是否显示。
<template>
<a-breadcrumb v-if="!qiankunWindow.__POWERED_BY_QIANKUN__" class="container-breadcrumb">
<a-breadcrumb-item>
<icon-apps />
</a-breadcrumb-item>
<a-breadcrumb-item v-for="item in items" :key="item">
{{ $t(item) }}
</a-breadcrumb-item>
</a-breadcrumb>
</template>
<script lang="ts" setup>
import { PropType } from 'vue';
import { qiankunWindow } from 'vite-plugin-qiankun/dist/helper';
defineProps({
items: {
type: Array as PropType<string[]>,
default() {
return [];
},
},
});
</script>
<style scoped lang="less">
.container-breadcrumb {
// margin: 16px 0;
background: #fff;
padding: 16px 15px;
box-shadow: 0px 1px 10px 0px #0000001a;
z-index: 9;
:deep(.arco-breadcrumb-item) {
color: rgb(var(--gray-6));
&:last-child {
color: rgb(var(--gray-8));
}
}
}
</style>
结语:qiankun
虽然暂时不支持vue3+vite+ts
项目,一步一个脚印,将qiankun
理解透彻,冷静思考,不断尝试,镇定梳理,总会得到解决办法,下满是我经过了五次尝试,第五次配置成功。下面四种进行配置的时候,都会有些许瑕疵,果断舍去。
最终效果:
存在问题
当基座的baseURL被修改后,响应的子应用的也会被修改,比如vision-mode/business-module
改成vision-web/business-module-vue3
,这时候我们就需要通过基座的props
方法,将基座的路径传递给子应用,子应用在app.mount
之前进行基础路径修改。
要求效果:
修改前路径地址:
修改后路径地址:
整改步骤
一、修改基座router/index.js
代码
export default new Router({
mode: 'history', // 去掉url中的#
base: 'vision-web',
scrollBehavior: () => ({ y: 0 }),
routes: constantRoutes
})
二、修改基座main.js
代码,将vision-mode/business-module
改为/vision-web/business-module-vue3
,通过通过props
将/vision-web/business-module-vue3
传递给子应用,在子应用app.mount
之前修改路由路径。
{
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: true, // 开启样式隔离
},
props: { sharedStore: store, baseName: '/vision-web/business-module-vue3' }
},
三、根据基座传递过来的baseName
在main.ts
修改子应用路由地址,子应用所有代码如下:
/* eslint-disable no-underscore-dangle */
import './public-path'
import { createApp } from 'vue';
import ArcoVue from '@arco-design/web-vue';
import ArcoVueIcon from '@arco-design/web-vue/es/icon';
import globalComponents from '@/components';
import { renderWithQiankun, qiankunWindow } from 'vite-plugin-qiankun/dist/helper';
import { createRouter, createWebHistory } from 'vue-router';
import router from './router';
import { appRoutes } from "./router/routes"
import { REDIRECT_MAIN, NOT_FOUND_ROUTE } from "./router/routes/base";
import store from './store';
import i18n from './locale';
import { setToken } from './utils/auth';
import directive from './directive';
import './mock';
import App from './App.vue';
import '@/assets/style/global.less';
import '@/api/interceptor';
// 第五版本
let app: any;
// 判断是否在qiankun环境下
if (!qiankunWindow.__POWERED_BY_QIANKUN__) {
app = createApp(App);
app.use(ArcoVue, {});
app.use(ArcoVueIcon);
app.use(router);
app.use(store);
app.use(i18n);
app.use(globalComponents);
app.use(directive);
const token = window.name
if (token) {
setToken(token)
router.push('/dashboard')
}
app.mount('#app');
} else {
renderWithQiankun({
// qiankun的生命周期,挂载
mount(props) {
console.log("~~~~~", props.baseName)
// 创建子应用独立的路由配置
const subAppRouter = createRouter({
history: createWebHistory(props.baseName), // 使用父应用传递的 baseName 创建 history
routes: [{
path: '/',
redirect: 'login',
},
{
path: '/login',
name: 'login',
component: () => import('@/views/login/index.vue'),
meta: {
requiresAuth: false,
},
},
...appRoutes,
REDIRECT_MAIN,
NOT_FOUND_ROUTE,], // 这里可以添加你的子应用路由配置
});
app = createApp(App);
// 传递的值可以获取到了
app.use(ArcoVue, {});
app.use(ArcoVueIcon);
// app.use(router);
app.use(subAppRouter)
app.use(store);
app.use(i18n);
app.use(globalComponents);
app.use(directive);
const token = window.name
if (token) {
setToken(token)
router.push('/dashboard')
}
app.mount(
props.container
? props.container.querySelector("#app")
: document.getElementById("app")
);
},
// 应用加载
bootstrap() {
console.log("--bootstrap");
},
// 修改
update(props) {
console.log("--update", props);
},
// 销毁
unmount() {
console.log("--unmount");
app?.unmount();
},
});
}
运行基座,就可以达到想要的效果了。
nginx代理问题
下面是vue项目中的default.conf文件
server {
listen 80;
#server_name nginx;
#root /usr/share/nginx/html;
location ^~/websocket/ {
proxy_pass http://xxx.xxx.xxx.xxx:xxx;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_connect_timeout 5;
proxy_read_timeout 90s;
proxy_send_timeout 10s;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
}
location ^~/api/visioncloud/ {
proxy_pass http://xxx.xxx.xxx.xxx:xxx/;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
}
location ^~/api/ {
proxy_pass http://xxx.xxx.xxx.xxx:xxx/;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
}
location /vision-web {
alias /usr/share/nginx/html/hsk-admin;
index index.html;
try_files $uri $uri/ /hsk-admin/index.html;
}
location /hsk-admin {
alias /usr/share/nginx/html/hsk-admin;
index index.html;
try_files $uri $uri/ /hsk-admin/index.html;
}
location /ai-system-web {
alias /usr/share/nginx/html/ai-system-web/dev/h5;
index index.html;
try_files $uri $uri/ /ai-system-web/dev/h5/index.html;
}
location /open-platform-web {
alias /usr/share/nginx/html/open-platform-web;
index index.html;
try_files $uri $uri/ /open-platform-web/index.html;
}
location /ai-blemish-screen {
alias /usr/share/nginx/html/ai-blemish-screen;
index index.html;
try_files $uri $uri/ /ai-blemish-screen/index.html;
}
location /business-module-vue2 {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods *;
add_header Access-Control-Allow-Headers *;
alias /usr/share/nginx/html/business-module-vue2;
index index.html;
try_files $uri $uri/ /business-module-vue2/index.html;
}
location /business-module-vue3 {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods *;
add_header Access-Control-Allow-Headers *;
alias /usr/share/nginx/html/business-module-vue3;
index index.html;
try_files $uri $uri/ /business-module-vue3/index.html;
}
location /system-module-vue2 {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods *;
add_header Access-Control-Allow-Headers *;
alias /usr/share/nginx/html/system-module-vue2;
index index.html;
try_files $uri $uri/ /system-module-vue2/index.html;
}
location /system-module-vue3 {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods *;
add_header Access-Control-Allow-Headers *;
alias /usr/share/nginx/html/system-module-vue3;
index index.html;
try_files $uri $uri/ /system-module-vue3/index.html;
}
location /park-model-web {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods *;
add_header Access-Control-Allow-Headers *;
alias /usr/share/nginx/html/park-model-web;
index index.html;
try_files $uri $uri/ /park-model-web/index.html;
}
location /prodectline-model-web {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods *;
add_header Access-Control-Allow-Headers *;
alias /usr/share/nginx/html/prodectline-model-web;
index index.html;
try_files $uri $uri/ /prodectline-model-web/index.html;
}
#location @router {
# 非静态资源,直接定向index.html
# rewrite ^.*$ /aps-admin/index.html last;
#}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
#location /nacos {
# proxy_pass http://nacosList;
#}
}
vue2基座配合vue3微应用完成~ 如果对你有帮助,给个小赞