vue 子路由不写router view可以显示吗_portal 在 vue 中的使用

本文探讨了在 Vue 中使用 Portal 来解决页面打印时排除布局元素的问题。通过介绍 Vue 的 Portal 库 portal-vue,展示了如何在隐藏布局的同时,保持业务页面的显示。文章详细解释了如何配置和利用 <portal> 和 <portal-target> 标签来实现在不同层级的 router-view 之间动态传输内容,以达到预期效果。

960bd653fbac58f92faa77e0a001a7c0.png

之前在学 React 的时候了解过 portal,简单的来说就是可以将子组件渲染到父组件以外的地方。官网上说 portal 的典型用例是当父组件有overflow: hiddenz-index样式时,但你需要子组件能够在视觉上“跳出”其容器。例如,对话框、悬浮卡以及提示框。

但是当时对此例并无太多感觉,这就导致了我一度以为这是个没什么卵用的 API,大多数的使用场景应该是在封装一些公共组件上。但是近期遇到的一个需求,却确确实实出现了能够使用这个 API 的场景。

做过后台管理系统都知道,一个后台管理系统中,最基础的结构就是布局。而我们的各个业务页面基本都是在布局中显示的,通过布局中的侧边栏/标签栏/面包屑进行页面切换。

而这些通过 vue 实现基本上都是用 vue-router。如下面两个页面:

import Layout from '@/layout'

export default [
  {
    name: 'Admin',
    path: '/admin',
    component: Layout,
    redirect: '/admin/role',
    meta: { title: '后台管理', icon: 'el-icon-monitor' },
    children: [
      {
        path: 'role',
        name: 'AdminRole',
        meta: { title: '角色管理', noCache: true },
        component: () => import('@/views/admin/role')
      },
      {
        path: 'user',
        name: 'AdminUser',
        meta: { title: '账号管理', noCache: true },
        component: () => import('@/views/admin/user')
      }
    ]
  }
]

如此便可以通过前端路由,实现对 layout 布局组件的复用。

对于不需要 layout 的页面,只需:

  {
    path: '/404',
    hidden: true,
    component: () => import('@/views-constant/error-page/404')
  }

不使用 layout 即可。而整个 app 其实就是一个个嵌套的 router-view。最外层的整个 app 是 router-view,里面的业务页面也是 router-view。

这样的架构,几乎就可以满足中小型后台的全部需求了。

但是,需求总是不可控的。

前些日子,后台需要做个页面。这个页面需要在管理员确认一些信息后,将页面内容打印下来。打印页面自然是用 window.print 方法就好。但是问题出在,window.print 会把整个页面都打印下来,其中就包括侧边栏/导航栏/面包屑这些属于布局的部分。因此要实现这个功能的核心就是在某些情况下,我们可以动态的控制 layout 是否显示。

但是实际上,通过 vue-router 我们知道。平时的业务页面都是 layout 的子组件。如果我们把 layout 隐藏,业务页面自然也会被隐藏,这可如何是好?

答案就是标题。

使用 portal 方法即可。在我们隐藏 layout 的时候,实际上就是隐藏项目最外层的那个 router-view。我们要做的就是隐藏最外面的 router-view 同时把当前页面 的 router-view 渲染到最外层 router-view 同级就好。

但是问题是 vue 并没有 portal API。不过好在有一个 vue 库叫做 portal-vue 可以帮助我们实现 portal 功能,用起来也十分简单:

import PortalVue from 'portal-vue'

Vue.use(PortalVue)

而使用起来只需要两个标签即可完成 portal 功能:

<portal><portal to="app" /> 和 <portal-target name="app"><portal-target />

如此便可以将 portal 内的节点渲染到 portal-target 中,两者通过 name&to 进行关联传送。无论两个组件的相对位置如何。

带入到我们的需求中,我们只需要将内部渲染业务页面的 router-view 传送渲染到最外部的 router-view 下面就好,具体代码如下:

<!-- 业务页面 -->
<template>
  <section class="app-main">
    <transition name="fade-transform" mode="out-in">
      <keep-alive :include="cachedViews">
        <portal v-if="$store.state.app.printing" to="app">
          <router-view :key="key" />
        </portal>
        <router-view v-else :key="key" />
      </keep-alive>
    </transition>
  </section>
</template>
<!-- 最外部的 router-view -->
<template>
  <div id="app">
    <router-view v-show="!$store.state.app.printing" />
    <portal-target name="app" />
  </div>
</template>

这两个页面通过 name&to 进行关联,通过 vuex 进行公共状态共享。

这样我们在需要的时候,将 vuex 中相应的 printing 置为 true。便可以将内部 router-view 的节点内容渲染到最外部的 router-view 同级。此时的效果就类似隐藏了布局,但是实际上 portal-vue 做的绝不是仅仅移动 dom 那么简单。portal-vue 做的是 vNode 层面的移动,有兴趣可以自行研究。

<think>好的,我现在需要帮助用户解决在Vue项目中点击按钮后弹窗打开指定路由页面的问题。用户还提到了参考引用,里面涉及Vue Router使用和弹窗的实现。首先,我需要理清楚用户的需求:他们想要在点击按钮时,是跳转到新页面,而是弹出一个窗口显示指定的路由内容。 首先,我应该考虑如何在离开当前页面的情况下显示另一个路由的内容。常规的路由跳转会替换当前页面,所以需要另一种方法。可能的方法包括使用Vue Router的组件动态渲染,或者利用Vue的弹窗组件来包裹目标路由的组件。 根据引用中的内容,Vue Router用于管理路由,并且可以通过编程式导航进行跳转。但用户需要的是弹窗显示,而是页面跳转。因此,可能需要结合弹窗组件和路由的组件渲染。比如,在弹窗中通过<router-view>来显示对应的路由组件,或者动态加载组件。 接下来,我需要考虑如何触发弹窗的显示。用户提到点击按钮,所以应该在按钮的点击事件中控制弹窗的显示状态。通常,这会使用一个布尔值的数据属性来控制弹窗的可见性。 另外,弹窗中需要显示指定路由对应的组件。这时候,可能需要通过路由配置获取组件,或者直接引入组件。如果使用路由配置,可以通过this.$router.options.routes找到对应的组件,但可能需要处理异步组件的情况。另一种方法是直接在弹窗中放置一个<router-view>,并通过路由的路径或名称来匹配,但这可能会影响当前页面的路由状态,因为主应用中可能已经有<router-view>了。 考虑到弹窗可能需要同时保持当前页面的状态,弹窗中的内容应该是独立于主路由的。因此,或许更好的方法是在弹窗中动态渲染目标组件,而是通过路由。例如,通过<component :is="componentName">来动态加载组件,但这需要知道组件的名称或直接引用组件。 过,用户可能希望利用路由的参数、嵌套路由或其他路由特性,所以使用路由的方式可能更合适。这时候,可以考虑在弹窗内部嵌套一个<router-view>,并为弹窗创建一个独立的路由上下文。过这可能比较复杂,尤其是需要处理路由守卫、嵌套路由等问题。 另一种思路是使用Vue Router的命名视图。例如,主页面有一个命名视图,弹窗部分使用另一个命名视图,通过路由配置来控制哪个视图显示哪个组件。但这种方法可能需要调整现有的路由配置,可能对现有结构影响较大。 或者,可以使用模态框(弹窗)来包裹目标组件,当点击按钮时,通过编程式导航获取目标组件的实例,并将其渲染到弹窗中。但这种方法可能需要手动管理组件的挂载和销毁。 参考用户提供的引用内容,其中提到使用router.push或window.open在新窗口打开页面。但用户需要的是弹窗,而是新窗口。所以可能需要结合弹窗组件和路由组件。 综合上述思路,可能的解决方案是: 1. 创建一个弹窗组件,用于显示目标路由的内容。 2. 在点击按钮时,通过编程式导航获取目标路由对应的组件,并在弹窗中渲染。 3. 或者,在弹窗中使用<router-view>,并通过路由配置将特定路径映射到弹窗中的视图。 但为了避免影响主路由,可能更简单的方法是将弹窗组件独立出来,手动加载目标组件。例如: - 创建一个弹窗组件,内部包含一个动态组件。 - 点击按钮时,根据路由路径或名称,获取对应的组件,并将其设置为弹窗的动态组件。 - 使用Vue Router的resolve方法解析路由,获取对应的组件。 过,Vue Router的resolve方法可能返回的是路由的位置信息,而是组件本身。所以可能需要另一种方式,例如从路由配置中查找对应的组件。 例如,在路由配置中,每个路由都有一个path和component属性。因此,可以通过遍历路由配置,找到匹配的path或name,然后获取对应的component。 然后,将这个组件动态加载到弹窗中。这样,当弹窗显示时,目标组件就会被渲染出来,而无需改变当前的路由状态。 过,需要注意的是,如果目标组件依赖于路由参数或路由实例,可能需要手动传递这些参数,或者在组件内部使用$route来获取信息。这时候,可能需要使用<router-view>来确保组件能够正确获取路由相关的上下文。 或者,可以创建一个新的路由实例,专用于弹窗内的内容,但这可能会增加复杂度。 另一个考虑是使用VuePortal(传送门)功能,将组件渲染到弹窗的DOM节点中,但这需要Vue版本支持或者使用第三方库如portal-vue。 综合以上分析,可能的最佳实践是: - 创建一个弹窗组件,控制显示隐藏。 - 在弹窗内部使用<component :is="currentComponent">动态渲染目标组件。 - 在点击按钮时,根据路由路径或名称,从路由配置中获取对应的组件,并设置为currentComponent。 - 同时,可能需要处理路由参数,例如通过props传递给目标组件。 接下来,具体的实现步骤可能包括: 1. 创建弹窗组件Modal.vue使用v-if或v-show控制显示,内部包含动态组件。 2. 在按钮的点击事件处理函数中,根据目标路由(如'/detail')找到对应的组件。 3. 将找到的组件设置为弹窗的动态组件,并显示弹窗。 4. 处理弹窗关闭时的清理工作。 关于如何从路由配置中获取组件,可以访问this.$router.options.routes,遍历找到匹配path或name的路由项,然后获取其component属性。如果component是懒加载的(() => import(...)),可能需要调用该函数来获取实际的组件。 例如: const targetRoute = this.$router.options.routes.find(route => route.path === '/target-path'); if (targetRoute) { const component = typeof targetRoute.component === 'function' ? (await targetRoute.component()).default : targetRoute.component; this.currentComponent = component; } 过,这可能需要处理异步组件的情况,使用async/await或者Promise。 此外,如果用户使用的是Vue 3,可能需要使用defineAsyncComponent或类似的方法来处理异步组件。 另外,如果路由配置中使用的是components(命名视图),可能需要调整获取方式。 另外,也可以考虑直接使用路由的name属性来查找路由配置,这样更稳定,即使path变化也会影响。 综上,示例代码可能如下: 在按钮所在的组件中: <template> <button @click="openDialog('/detail')">打开弹窗</button> <modal :show="showModal" @close="showModal = false"> <component :is="targetComponent" /> </modal> </template> <script> import { routes } from '../router' // 导入路由配置 export default { data() { return { showModal: false, targetComponent: null } }, methods: { async openDialog(path) { const route = routes.find(r => r.path === path) if (route) { let component = route.component if (typeof component === 'function') { component = (await component()).default } this.targetComponent = component this.showModal = true } } } } </script> 然后,Modal组件负责显示和隐藏,以及样式。 但这种方法需要确保路由配置中的component可以直接导入,或者处理懒加载的情况。此外,可能需要将路由配置单独导出,以便在组件中导入。 过,在实际项目中,路由配置可能是在router/index.js中导出的,所以需要正确导入。例如: import router from '../router' // 然后访问router.options.routes 或者,在Vue实例中,可以通过this.$router.options.routes访问。 过,在用户提供的引用[2]中,创建路由对象时传入了routes,因此可以通过这种方式获取。 另外,动态加载组件时,如果组件依赖路由的上下文(如this.$route.params),可能需要手动传递这些参数,或者在组件内部使用props接收。 例如,目标组件可能需要接收id参数作为prop,这样在弹窗中渲染时,需要手动传递这些参数。或者,可能需要使用Vue Router提供的<router-view>来确保组件能正确获取路由信息,但这又回到了如何在改变当前路由的情况下显示的问题。 另一种方法是使用Vue Router的<router-view>嵌套,在弹窗内部设置一个<router-view>,并通过路由的嵌套或命名视图来显示组件。例如: 在路由配置中,为弹窗定义一个命名视图: { path: '/detail', components: { default: DetailComponent, modal: DetailComponent } } 然后在弹窗中使用: <router-view name="modal" /> 但这种方法需要在跳转时指定命名视图,例如: this.$router.push({ path: '/detail', query: { modal: true } }) 但可能比较复杂,并且需要调整现有的路由结构。 综上,可能更简单的方法是直接动态加载组件到弹窗中,而是通过路由。但需要考虑组件是否需要路由相关的功能。 如果目标组件依赖于当前路由的参数或状态,那么直接渲染组件即可。否则,可能需要手动传递必要的参数,或者在组件内部使用Vuex/Pinia来管理状态。 最后,总结实现步骤: 1. 创建弹窗组件,控制显示隐藏。 2. 在按钮点击时,根据路由路径或名称找到对应的组件。 3. 处理异步组件(如果有的话)。 4. 将组件设置为弹窗的动态组件,并显示弹窗。 5. 在弹窗关闭时,清理动态组件。 另外,用户提供的引用中有关于使用window.open的例子,但用户希望的是弹窗,而是新窗口。因此,需要确保弹窗在当前页面内显示。 此外,如果用户希望在弹窗中保留路由的功能(例如,前进后退按钮影响弹窗内容),可能需要更复杂的路由管理,但根据用户需求,可能仅需静态显示目标组件即可。 因此,示例代码可能如下: 在utils.js中导出toPage方法,用于解析路由: export function getRouteComponent(path) { const route = router.options.routes.find(r => r.path === path); return route?.component; } 在组件中: <template> <button @click="openModal('/detail')">打开弹窗</button> <modal v-if="showModal" @close="showModal = false"> <component :is="modalComponent" /> </modal> </template> <script> import { getRouteComponent } from './utils'; import Modal from './Modal.vue'; export default { components: { Modal }, data() { return { showModal: false, modalComponent: null }; }, methods: { async openModal(path) { const component = getRouteComponent(path); if (typeof component === 'function') { this.modalComponent = (await component()).default; } else { this.modalComponent = component; } this.showModal = true; } } }; </script> 这样,当点击按钮时,会动态加载目标组件并在弹窗中显示。需要注意处理异步组件的情况,例如使用async/await。 此外,弹窗组件Modal需要处理关闭事件,例如: <template> <div class="modal" v-if="show"> <div class="modal-content"> <span class="close" @click="$emit('close')">×</span> <slot></slot> </div> </div> </template> <script> export default { props: ['show'], emits: ['close'] }; </script> 最后,用户可能需要处理样式,如定位、遮罩层等,这属于前端样式问题,在逻辑实现范围内。 综上,这样的实现方法应该能够满足用户的需求:点击按钮,弹窗显示指定路由对应的组件内容,而无需跳转页面或打开新窗口。同时,代码结构清晰,符合Vue使用习惯。</think>### Vue点击按钮弹窗打开指定路由页面实现方法 在Vue项目中,可通过**动态组件**和**路由解析**实现点击按钮后弹窗显示指定路由页面。以下是具体实现步骤: --- #### 1. 创建弹窗组件 新建`Modal.vue`组件,用于控制弹窗显示与内容承载: ```vue <template> <div v-if="show" class="modal-mask"> <div class="modal-wrapper"> <div class="modal-container"> <div class="modal-header"> <slot name="header"></slot> <button @click="$emit('close')" class="close-btn">×</button> </div> <div class="modal-body"> <slot></slot> </div> </div> </div> </div> </template> <script> export default { props: ['show'], emits: ['close'] } </script> <style scoped> .modal-mask { position: fixed; z-index: 9998; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); display: flex; justify-content: center; align-items: center; } .modal-container { background: white; padding: 20px; border-radius: 8px; width: 80%; max-width: 600px; } .close-btn { float: right; cursor: pointer; } </style> ``` --- #### 2. 动态加载路由组件 在按钮所在页面中,通过路由配置解析目标组件: ```vue <template> <button @click="openModal('/detail')">打开详情页弹窗</button> <Modal :show="showModal" @close="showModal = false"> <component :is="targetComponent" /> </Modal> </template> <script> import Modal from './Modal.vue' import router from '../router' // 导入路由实例 export default { components: { Modal }, data() { return { showModal: false, targetComponent: null } }, methods: { async openModal(path) { // 从路由配置中查找目标组件 const route = router.options.routes.find(r => r.path === path) if (route) { let component = route.component // 处理异步组件(如懒加载) if (typeof component === 'function') { component = (await component()).default } this.targetComponent = component this.showModal = true } } } } </script> ``` --- #### 3. 路由配置示例 确保路由文件中已定义目标路由: ```javascript // router/index.js const routes = [ { path: '/detail', name: 'Detail', component: () => import('@/views/Detail.vue') // 支持懒加载 } ] ``` --- #### 关键点说明 1. **动态组件**:通过`<component :is>`动态渲染目标组件。 2. **路由解析**:从路由配置中获取组件定义,支持同步/异步加载。 3. **弹窗控制**:使用`v-if`或`v-show`管理弹窗显示状态。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值