• vue项目嵌套(vue2嵌套vue3)


    实现思路

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    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)
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    问题总结及解决方案

    左侧菜单路由高亮状态

    展示产品库页面时,对应左侧菜单路由要相应高亮选中。
    在监听路由跳转时,判断当跳转产品库页面时,取 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> 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

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

    展示产品库页面时,对应上方面包屑导航要显示正确的路由名称。
    在监听路由跳转时,判断当跳转产品库页面时,取 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> 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    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;
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

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

    // 产品库
    <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> 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    产品库判断当前项目是在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> 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
  • 相关阅读:
    数字化时代——如何快速实现数字化转型并进入低代码赛道
    java基础 --- 关键字 final、this、super、static
    Nodered系列—使用mqtt写入国产数据库tDengine
    计算机网络知识之交换机、路由器、网关、MAC地址
    14.信号量的代码实现
    【Overload游戏引擎细节分析】从视图投影矩阵提取视锥体及overload对视锥体的封装
    JuiceFS分布式文件系统源码分析(Java层)
    MySQL关键字的执行顺序分析
    linux相关指令
    【计算机网络笔记】DHCP协议
  • 原文地址:https://blog.csdn.net/qq_53225741/article/details/125486536