vue项目嵌套(vue2嵌套vue3)

1,229 阅读2分钟

实现思路

  1. 产品测评通过 iframe 将产品库加载进来。
  2. 创建 template.vue 中间页,使用path参数携带产品库url,并通过传递用户信息和路由信息实现产品库自动登陆。
  3. 使用 postMessgae API 实现父子项目间的数据通信。

实现步骤

  1. 创建 Store/template 模块,定义产品库相关数据。
// src/store/modlues/template.ts

const state = {
  sign: 'productLibrary', // 产品库路由标识
  routePath: '/template', // 产品库跳转中间页path
  isFullFrame: false, // 是否设置产品库Iframe全屏
  NODE_ENV: process.env.NODE_ENV, // 环境变量
  ENV_URLS: {
    development: 'http://127.0.0.1:3000',
    test: 'http://cms-test.shenlanbao.com',
    uat: 'http://cms-uat.shenlanbao.com',
    production: 'http://cms.shenlanbao.com',
  },
};

const mutations: MutationTree<any> = {
  SET_isFullFrame(state, val) {
    state.isFullFrame = val;
  },
};
  1. 创建 template.vue 中间跳转页路由。
// src/router/modules/page-productLibrary-router.ts

{
    path: '/template',		// 对应Store/template.ts 的 routePath
    name: 'productLibrary',   // 对应Store/template.ts 的 sign
    cn: '产品库',
    hidden: true,
    component: () => import('@/views/productLibrary/template.vue'),
    children: [],
  },
  1. 点击左侧菜单将符合条件的路由跳转到产品库。
// src/components/common/MenuItem.vue

<script>
  methods: {
    navigateTo(e): void {
      const { frontPermission, url } = e;
      
      const { sign, routePath } = this.$store.state.template;
	 		
      if (frontPermission.includes(sign)) {
        if (!url) return;
        this.$router.push({
          path: routePath,
          query: { path: url },
        });
      } else {
        if (this.$route.path === url) return;
        this.$router.push({ path: url });
      }
    },
  }
</script>
  1. template.vue 监听路由并更新产品库路由,同时传递用户登陆信息实现自动登陆
// src/views/productLibrary/template.vue

<div :class="{ wrapper: true, hidemenu: $store.state.template.isFullFrame }">
	<iframe
	  :src="src"
	  frameborder="0"
	  class="sub-frame"
	  ref="frames"
	  @load="frameLoad"
	></iframe>
</div>

<script lang="ts">

@Component
export default class Index extends Vue {
  @Ref() readonly frames;
  src: string = ''; // 产品库跳转路由

  // 监听路由,更新产品库路由
  @Watch('$route')
  toggleRoute(data) {
    this.$nextTick(() => {
      this.frameLoad();
    });
  }

  frameLoad() {
    const { path = '' } = this.$route.query;
    const auth = localStorage.getItem('auth') || '';
    if (!path || !auth) return;

    this.setSrc(path); // 设置产品库跳转路由
    const productLibraryRoutes = this.getRoutes(); // 过滤产品库路由

    const permission = {
      auth,
      path,
      userInfo: localStorage.getItem('userInfo'),
      routes: productLibraryRoutes[0].childPermissionResList,
    };
	  // 传递用户登陆信息实现自动登陆
    this.frames.contentWindow.postMessage(permission, '*');
  }

  getRoutes() {
    const localRoutes = localStorage.getItem('routes') || '';
    if (!localRoutes) return '';

    const { sign } = this.$store.state.template; // 产品库跳转路由标识
    const routes: any[] = JSON.parse(localRoutes) || [];
    return routes.filter((i) => {
      return i.frontPermission === sign &amp;&amp; i.childPermissionResList;
    });
  }

  setSrc(path) {
    const { NODE_ENV, ENV_URLS } = this.$store.state.template;

    const domain = ENV_URLS[NODE_ENV];
    if (path &amp;&amp; domain) this.src = `${domain}${path}`;
  }
}
</script>
  1. 产品库获取登陆信息及路由信息进行模拟登陆操作。
// index.html

<body>
  <div id="app"></div>
  <script type="module" src="/src/main.ts"></script>
  <script>
    window.addEventListener(
      'message',
      function (event) {
        const {
          data
        } = event
        if (data.auth) {
          window.localStorage.setItem('auth', data.auth)
          window.localStorage.setItem('routes', JSON.stringify(data.routes))
          window.localStorage.setItem('userInfo', data.userInfo)
          window.localStorage.setItem('path', data.path)
        }
      },
      false,
    )
  </script>
</body>


// src/router/routerGuards.ts
export function createRouterGuards(routers: Router) {
  routers.beforeEach(async (to, _, next) => {
    const isFrame = JSON.parse(localStorage.getItem('isFrame') || '') // 是否是 iframe 加载
    if (!isFrame) {
      // 正常登陆操作
    } else {
      const hasToken = localStorage.getItem('auth') || ''
      const { addRouter } = store.state.global
      if (hasToken &amp;&amp; addRouter) {
        next()
        return
      }
      localStorage.setItem('auth', localStorage.getItem('auth') || '')
      await store.commit('global/setRoutes', JSON.parse(localStorage.getItem('routes') || ''))
      await store.commit('global/setUserInfo', JSON.parse(localStorage.getItem('userInfo') || ''))
      // 路由添加/标记
      store.commit('global/isAddRouter', true)
      const getNewRouter = store.getters['global/getNewRouter']
      getNewRouter.forEach((route: Iroute) => {
        routers.addRoute(route)
      })
      const path = localStorage.getItem('path')
      to.path = path
      const newTo = {
        ...to,
        name: to.name || undefined,
      }
      next({ ...newTo, replace: true })
    }
  })
}
  1. 产品库向产品测试进行通信
// src/utils/template.ts

// ifarame 内嵌时向产品测评窗口进行通信事件定义
const source = 'productLibrary' // 产品库跳转路由标识

const posMessage = function (config) {
  const message = Object.assign({ source }, config)

  return new Promise((resolve) => {
    top.postMessage(message, '*')
    resolve(true)
  })
}

// 是否设置全屏遮罩
export function postFrameCover(val) {
  const config = {
    fnName: 'setIframeCover',
    params: val,
  }
  return posMessage(config)
}

// 跳转产品测评页面
export function postTopRouter(path) {
  if (!path) return
  const config = {
    fnName: 'setRoute',
    params: path,
  }
  return posMessage(config)
}

问题总结及解决方案

左侧菜单路由高亮状态

展示产品库页面时,对应左侧菜单路由要相应高亮选中。
在监听路由跳转时,判断当跳转产品库页面时,取 query 中的 path 字段为 url 做高亮处理。

// src/components/Nav.vue

<script>
@Watch('$route', { immediate: true })
  handlerRouteChange(to: Route): void {
    this.active = to.path;
    this.setProductRoute(to); // 判断产品库路由
  }

  setProductRoute(to) {
    const { name = '', query = {} } = to;
    const { sign } = this.$store.state.template; // 产品库跳转路由标识
    const isProductLibRoute = name === sign &amp;&amp; query.path;
    if (isProductLibRoute) {
      this.active = query.path;
    }
  }
</script>

产品测评面包屑导航页面路径

展示产品库页面时,对应上方面包屑导航要显示正确的路由名称。
在监听路由跳转时,判断当跳转产品库页面时,取 query 中的 path 字段为 url 做高亮处理。

// src/components/common/BaseHeard.vue

<script>
@Watch('$route')
  $routeWatch(to: Iobj) {
    this.getBar(to);
  }
  getBar(route) {
    let pathName = this.getRoutePath(route);

    let name = '';
    let barList: Array<Iobj> = [];
    let pathArr = pathName.split('/').filter((_) => _);
    let newArr = [
      ...this.getNewRouter,
      ...(this.$router as any).options.routes,
    ];
    for (let i of pathArr) {
      name = name + `/${i}`;
      for (let j of newArr) {
        if (j.path === name) {
          barList.push({
            cn: j.cn,
            path: j.path,
          });
          break;
        }
      }
    }
    this.arr = barList;
  }

  // 获取当前路由path
  getRoutePath(route) {
    let { path: pathName, query } = route;
    const { routePath } = this.$store.state.template;
    // 如果产品库的路由,修改为取query下的path为路由名称
    if (pathName === routePath) {
      const { path = '' } = query;
      if (path) return path;
    } else {
      return pathName;
    }
  }
</script>

Dialog弹出窗遮罩覆盖

当点击弹窗时,通知父窗口,同步修改外层样式。

// 产品库
<el-button @click="handleMask()">测试弹窗</el-button>

<script>
import { postFrameCover } from '@utils/template'
handleMask() {
  postFrameCover(true).then(() => {
	  this.showDialog = true
  })
}
</script>


// 产品测评 template.vue
setIframeCover(val) {
  this.$store.commit('template/SET_isFullFrame', val);
}

// 产品测评 App.vue
// 修改左侧菜单的z-index,使遮罩在最顶层
<nav class="nav" :class="{ hidemenu: $store.state.template.isFullFrame }">
  <app-nav />
</nav>

.hidemenu {
  background: rgba(0, 0, 0, 0.5);
  z-index: 100;
}

// 产品测评 template.vue
<div :class="{ wrapper: true, hidemenu: $store.state.template.isFullFrame }">
	<iframe
	  :src="src"
	  frameborder="0"
	  class="sub-frame"
	  ref="frames"
	  @load="frameLoad"
	></iframe>
</div>

.hidemenu {
  background: rgba(0, 0, 0, 0.5);
  z-index: 100;
}

产品库子项目跳转产品测评页面

// 产品库
<el-button type="primary" @click="handleArticle()">跳转产品测评文章详情</el-button>

<script>
import { postTopRouter } from '@utils/template'
handleArticle() {
  const route = {
	path: '/article/articleAdd',
	query: {
	  id: '795964024911646720',
	},
  }
  postTopRouter(route)
}
</script>

产品库判断当前项目是在iframe中还是web中

// index.html
<head>
  <script>
    // 判断是在iframe还是web
    let isFrame = false
    if (window.frames.length != parent.frames.length) isFrame = true
    window.localStorage.setItem('isFrame', isFrame)
  </script>
</head>