浏览器tab开多个,每个tab是一个模块。如何设置?

浏览器tab开多个,每个tab是一个模块。如何设置?
技术是vue2脚手架
业务需求:用户登录之后显示N个卡片,每个卡片为一个模块,
如项目模块,报表模块,
每个模块下方有N个链接,如项目模块-》项目列表A,项目列表B,报表模块-》报表类型C,报表类型D。
点击A,检测当前浏览器有没有开项目模块,如果没有就开一个新的浏览器tab,并且打开A界面
如果有项目模块就在项目模块tab页,经过路由跳转到点击得A路径

总的来说就是 属于 项目得路径只能在项目浏览器tab内部跳,属于报表得只能在报表tab跳,

阅读 525
avatarAI BotBETA

要实现这种功能,你可以使用JavaScript和一些现代Web技术(如单页应用框架、浏览器存储、以及浏览器API)来管理多个浏览器标签页(tabs)和模块之间的导航。以下是一个简化的解决方案思路,使用单页应用(SPA)框架(如React或Vue.js)和浏览器本地存储(如localStoragesessionStorage)来跟踪打开的标签页。

解决方案思路

  1. 使用SPA框架

    • 创建一个SPA应用,以便在客户端管理路由和组件。
  2. 模块和路由

    • 定义每个模块(如项目模块、报表模块)的路由和组件。
  3. 标签页管理

    • 使用一个对象或Map来跟踪哪些模块已经在哪些标签页中打开。
    • 当用户点击链接时,检查该链接所属的模块是否已经在某个标签页中打开。
  4. 打开新标签页或导航到现有标签页

    • 如果模块尚未打开,则使用window.open在新标签页中打开该模块的首页。
    • 如果模块已经打开,则使用SPA的路由机制在当前标签页内导航到相应的路径。
  5. 跨标签页通信(可选):

    • 如果需要在不同标签页之间共享状态或通信,可以使用BroadcastChannellocalStorage的变化监听等机制。

示例代码(React为例)

以下是一个简化的React示例,演示了如何管理模块和标签页:

// 假设你有一个React应用,并且已经设置了路由(如使用react-router-dom)

// 创建一个全局状态来跟踪打开的标签页
const tabState = {
  projectModuleTab: null, // 存储项目模块标签页的窗口对象或null
  reportModuleTab: null,  // 存储报表模块标签页的窗口对象或null
};

// 一个函数,用于检查并打开/导航到相应的模块标签页
function navigateToModule(moduleName, path) {
  const currentTab = tabState[moduleName + 'ModuleTab'];

  if (currentTab) {
    // 模块已经在某个标签页中打开,使用postMessage发送消息(如果需要)并导航到路径
    currentTab.postMessage({ type: 'NAVIGATE', path: path });
    // 你可能还需要在当前窗口或标签页中做一些UI更新(如高亮选中的链接)
  } else {
    // 模块尚未打开,打开一个新的标签页
    const newTab = window.open(`/${moduleName}/${path}`, `_blank`);
    if (newTab) {
      // 存储新标签页的窗口对象到全局状态
      tabState[moduleName + 'ModuleTab'] = newTab;

      // 可选:监听新标签页的消息(例如,用于关闭标签页时的清理)
      newTab.addEventListener('message', (event) => {
        if (event.data.type === 'CLOSED') {
          tabState[moduleName + 'ModuleTab'] = null;
        }
      });

      // 注意:由于安全限制,新标签页可能无法直接与其打开者通信,除非它们来自相同的源。
      // 在这种情况下,你可能需要使用其他机制(如服务器端的WebSocket或轮询)来同步状态。
    }
  }
}

// 在你的组件中调用这个函数
function LinkComponent({ moduleName, path, children }) {
  return (
    <a href="#" onClick={(e) => {
      e.preventDefault();
      navigateToModule(moduleName, path);
    }}>{children}</a>
  );
}

// 示例使用
function App() {
  return (
    <div>
      <LinkComponent moduleName="project" path="listA">项目列表A</LinkComponent>
      <LinkComponent moduleName="project" path="listB">项目列表B</LinkComponent>
      <LinkComponent moduleName="report" path="typeC">报表类型C</LinkComponent>
      <LinkComponent moduleName="report" path="typeD">报表类型D</LinkComponent>
    </div>
  );
}

// 注意:这个示例代码非常简化,并且没有处理所有可能的边缘情况和错误处理。
// 在实际生产环境中,你需要添加更多的逻辑来处理例如标签页关闭、用户刷新页面、跨域通信限制等问题。

注意事项

  • 安全性:由于浏览器的同源策略,跨标签页的通信可能会受到限制。确保你的应用能够处理这些限制。
  • 状态管理:考虑使用更复杂的状态管理库(如Redux)来管理跨组件和跨标签页的状态。
  • 用户体验:提供清晰的UI反馈,让用户知道哪个模块在哪个标签页中打开。
  • 错误处理:处理可能的错误情况,如window.open被浏览器阻止的情况。
1 个回答

先配一下路由
router/index.js

import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'

Vue.use(VueRouter)

const projectRoutes = [
  {
    path: '/project/list-a',
    name: 'ProjectListA',
    component: () => import('../views/project/ListA.vue'),
    meta: { module: 'project' }
  },
  {
    path: '/project/list-b',
    name: 'ProjectListB',
    component: () => import('../views/project/ListB.vue'),
    meta: { module: 'project' }
  }
]

const reportRoutes = [
  {
    path: '/report/type-c',
    name: 'ReportTypeC',
    component: () => import('../views/report/TypeC.vue'),
    meta: { module: 'report' }
  },
  {
    path: '/report/type-d',
    name: 'ReportTypeD',
    component: () => import('../views/report/TypeD.vue'),
    meta: { module: 'report' }
  }
]

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  ...projectRoutes,
  ...reportRoutes
]

const router = new VueRouter({
  routes
})

export default router

主界面 (views/Home.vue):

<template>
  <div class="home">
    <div class="card-container">
      <div v-for="(module, key) in modules" :key="key" class="module-card">
        <h3>{{ module.name }}</h3>
        <div class="module-links">
          <a 
            v-for="link in module.links" 
            :key="link.path"
            href="javascript:void(0)"
            @click="handleLinkClick(key, link.path)"
          >
            {{ link.name }}
          </a>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'Home',
  data() {
    return {
      modules: {
        project: {
          name: '项目模块',
          links: [
            { name: '项目列表A', path: '/project/list-a' },
            { name: '项目列表B', path: '/project/list-b' }
          ]
        },
        report: {
          name: '报表模块',
          links: [
            { name: '报表类型C', path: '/report/type-c' },
            { name: '报表类型D', path: '/report/type-d' }
          ]
        }
      }
    }
  },
  methods: {
    handleLinkClick(moduleKey, path) {
      const windowName = `module_${moduleKey}`
      const existingWindow = window.open('', windowName)

      if (existingWindow && !existingWindow.closed) {
    
        localStorage.setItem('navigationEvent', JSON.stringify({
          moduleKey,
          path,
          timestamp: Date.now()
        }))
      } else {
        const baseUrl = process.env.BASE_URL || '/'
        window.open(`${baseUrl}#${path}`, windowName)
      }
    }
  }
}
</script>

<style scoped>
.card-container {
  display: flex;
  flex-wrap: wrap;
  gap: 20px;
  padding: 20px;
}

.module-card {
  border: 1px solid #ddd;
  padding: 15px;
  border-radius: 8px;
  width: 250px;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}

.module-links {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 10px;
}

.module-links a {
  color: #0066cc;
  text-decoration: none;
  cursor: pointer;
}

.module-links a:hover {
  text-decoration: underline;
}
</style>

App.vue:

<template>
  <div id="app">
    <router-view/>
  </div>
</template>

<script>
export default {
  name: 'App',
  created() {
    
    if (window.name.startsWith('module_')) {
      this.listenForNavigationEvents()
    }
  },
  methods: {
    getCurrentModule() {
      return window.name.split('_')[1]
    },
    listenForNavigationEvents() {
      window.addEventListener('storage', (e) => {
        if (e.key === 'navigationEvent') {
          const data = JSON.parse(e.newValue)
          const currentModule = this.getCurrentModule()

          if (data.moduleKey === currentModule) {
            this.$router.push(data.path)
          }
        }
      })
    }
  }
}
</script>

其中一个模块 (views/project/ListA.vue):


<template>
  <div class="project-list-a">
    <h2>项目列表A</h2>
    <!-- 页面内容 -->
  </div>
</template>

<script>
export default {
  name: 'ProjectListA',
  created() {
   
    this.validateModule()
  },
  methods: {
    validateModule() {
      const currentModule = window.name.split('_')[1]
      const routeModule = this.$route.meta.module

      if (currentModule && routeModule && currentModule !== routeModule) {
       
        const windowName = `module_${routeModule}`
        const existingWindow = window.open('', windowName)

        if (existingWindow && !existingWindow.closed) {
          localStorage.setItem('navigationEvent', JSON.stringify({
            moduleKey: routeModule,
            path: this.$route.path,
            timestamp: Date.now()
          }))
        } else {
          window.open(`${process.env.BASE_URL}#${this.$route.path}`, windowName)
        }
        
        if (window.history.length > 1) {
          this.$router.go(-1)
        } else {
          window.close()
        }
      }
    }
  }
}
</script>

在main.js中添加导航守卫:

import Vue from 'vue'
import App from './App.vue'
import router from './router'

Vue.config.productionTip = false

router.beforeEach((to, from, next) => {

  if (window.name.startsWith('module_')) {
    const currentModule = window.name.split('_')[1]
    const routeModule = to.meta.module

    if (routeModule && currentModule !== routeModule) {
     
      next(false)
      return
    }
  }
  next()
})

new Vue({
  router,
  render: h => h(App)
}).$mount('#app')

建一个混入 (mixins/moduleValidation.js):

export default {
  created() {
    this.validateModule()
  },
  methods: {
    validateModule() {
      const currentModule = window.name.split('_')[1]
      const routeModule = this.$route.meta.module

      if (currentModule && routeModule && currentModule !== routeModule) {
        const windowName = `module_${routeModule}`
        const existingWindow = window.open('', windowName)

        if (existingWindow && !existingWindow.closed) {
          localStorage.setItem('navigationEvent', JSON.stringify({
            moduleKey: routeModule,
            path: this.$route.path,
            timestamp: Date.now()
          }))
        } else {
          window.open(`${process.env.BASE_URL}#${this.$route.path}`, windowName)
        }
        
        if (window.history.length > 1) {
          this.$router.go(-1)
        } else {
          window.close()
        }
      }
    }
  }
}

用混入:

<script>
import moduleValidation from '@/mixins/moduleValidation'

export default {
  name: 'ProjectListA',
  mixins: [moduleValidation],
  // ... 其他配置
}
</script>
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏