我使用 vue-cli 生成了一个项目。我看到项目有一个 App.vue 这是应用程序的主要布局 - 如果我没记错的话。在这里,我放置了我的基本 HTML 布局和 <router-view></router-view>
。现在的问题是我需要完全不同的登录布局(不同的包装器,body 有不同的类),但我无法更改它,因为 App.vue 的模板有点“固定”作为布局。如何处理这个问题?有推荐的方法吗?
我是否应该创建表示布局的新组件,这样在这种情况下,我的 App.vue 模板将只有 <router-view></router-view>
,然后 LoginLayout.vue 将包含在其中?
vue
管理布局的有趣选项:markus.oberlehner.net/blog/dynamic-vue-layout-components
我想我找到了解决办法。该方法的 App.vue
仅包含 <router-view></router-view>
,然后包含表示布局的不同组件(如果需要,包含 <router-view>
和子路由)。我找到了一个以这种方式使用它的项目here。
我认为它使事情更加干净和有条理。恕我直言,隐藏所有定义布局结构的元素(所有 div)太混乱了——尤其是对于更大的应用程序。
一个很好的解决方案是使用 slots
首先创建你的“布局组件”
src/components/layouts/basic.vue
<template>
<div class="basic-layout">
<header>[Company logo]</header>
<hr>
<slot/>
<hr>
<footer>
Made with ❤ at Acme
</footer>
</div>
</template>
然后在另一个组件中使用它:
<template>
<layout-basic>
<p>Hello world!</p>
</layout-basic>
</template>
<script>
import LayoutBasic from '@/components/layouts/basic'
export default {
components: {
LayoutBasic
}
}
</script>
“Hello world”将出现在 <slot/>
标记所在的位置。
您还可以拥有多个具有名称的插槽,请参阅 complete docs。
Although, in terms of flexibility, this approach has everything we need, there is one huge downside of wrapping our views in a static layout component: the component is destroyed and re-created every time the route changes.
利用路由,尤其是子路由是在 Vue 中实现通用布局的好方法。
所有这些代码都使用 Vue 2.x
首先有一个非常简单的名为 App 的 vue 组件,它没有布局。
应用程序.vue
<template>
<router-view></router-view>
</template>
然后有一个 Routes 文件,你将把它带入你的 Vue 实例。
路线。(ts|js)
import Vue from 'vue'
import VueRouter from 'vue-router'
const NotFoundComponent = () => import('./components/global/notfound.vue')
const Login = () => import('./components/account/login.vue')
const Catalog = () => import('./components/catalog/catalog.vue')
export default new VueRouter({
mode: 'history',
linkActiveClass: 'is-active',
routes: [
//Account
{ path: '/account', component: () => import('./components/account/layout.vue'),
children: [
{ path: '', component: Login },
{ path: 'login', component: Login, alias: '/login' },
{ path: 'logout',
beforeEnter (to: any, from: any, next: any) {
//do logout logic
next('/');
}
},
{ path: 'register', component: () => import('./components/account/register.vue') }
]
},
//Catalog (last because want NotFound to use catalog's layout)
{ path: '/', component: () => import('./components/catalog/layout.vue'),
children: [
{ path: '', component: Catalog },
{ path: 'catalog', component: Catalog },
{ path: 'category/:id', component: () => import('./components/catalog/category.vue') },
{ path: 'product', component: () => import('./components/catalog/product.vue') },
{ path: 'search', component: () => import(`./components/catalog/search.vue`)} ,
{ path: 'basket', component: () => import(`./components/catalog/basket.vue`)} ,
{ path: '*', component: NotFoundComponent }
]
}
]
})
该代码使用延迟加载(使用 webpack),所以不要让 () => import(...)
抛出你。如果您想要预先加载,它可能只是 import(...)
。
重要的是儿童路线。所以我们将 /account
的主路径设置为使用 /components/account/layout.vue
,但前两个子节点指定主要内容 vue(登录)。我选择这样做是因为如果有人只是浏览到 /account 我想用登录屏幕问候他们。 /account 可能适合您的应用程序作为登录页面,他们可以在其中检查订单历史记录、更改密码等...
我对目录做了同样的事情... /
和 /catalog
都使用 /catalog/catalog
文件加载 catalog/layout
。
另请注意,如果您不喜欢拥有“子文件夹”(即帐户/登录名而不仅仅是/登录名)的想法,那么您可以使用我在登录名中显示的别名。
添加 , alias: '/login'
意味着即使实际路径是 /account/login
,用户也可以浏览到 /login
。
这是整个事情的关键,但只是为了尝试使示例完整......
这是我的启动文件,它连接了我的 app.vue 和路由:
启动。(ts|js)
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
import App from './components/app.vue';
import router from './routes';
new Vue({
el: '#app',
router,
render: h => h(App)
});
我为我的应用程序的每个主要部分(帐户、目录等)创建了一个 layout.vue 文件。
帐户/layout.vue
<template>
<div>
<cc-header></cc-header>
<div class="container">
<main>
<router-view></router-view>
</main>
<aside>
</aside>
</div>
<cc-footer></cc-footer>
</div>
</template>
<script lang="ts">
import ccHeader from "../common/cc-header.vue"
import ccFooter from "../common/cc-footer.vue"
export default {
components: {
ccHeader,
ccFooter
}
}
</script>
<style lang="scss" scoped>
.container {
display: flex;
}
main {
flex: 3;
order: 2;
}
aside {
flex: 1;
order: 1;
}
</style>
和目录的布局......
目录/layout.vue
<template>
<div>
<cc-header></cc-header>
<div class="catalog-container">
<main class="catalog">
<router-view></router-view>
</main>
<cc-categories></cc-categories>
</div>
<cc-footer></cc-footer>
</div>
</template>
<script lang="ts">
import ccHeader from "../common/cc-header.vue"
import ccFooter from "../common/cc-footer.vue"
import ccCategories from "./cc-categories.vue"
export default {
components: {
ccCategories,
ccHeader,
ccFooter
},
data : function() : any {
return {
search: ''
}
},
}
</script>
<style lang="scss" scoped>
.catalog-container {
display: flex;
}
.category-nav {
flex: 1;
order: 1;
}
.catalog {
flex: 3;
order: 2;
}
</style>
两种布局都使用常见的组件,如页眉和页脚,但它们不需要。目录布局在侧导航中有类别,而帐户布局没有。我把我的常用组件放在 components/common 下。
常见/页脚.vue
<template>
<div>
<hr />
<footer>
<div class="footer-copyright">
<div>© Copyright {{year}} GlobalCove Technologies, LLC</div>
<div>All rights reserved. Powered by CoveCommerce.</div>
</div>
</footer>
</div>
</template>
<script lang="ts">
import Vue from "vue";
export default Vue.component('cc-footer', {
data : function() : any {
return {
year: new Date().getFullYear()
}
},
})
</script>
<style lang="scss">
</style>
整体文件结构
src/
boot.ts
routes.ts
components/
app.vue
catalog/
layout.vue
catalog.vue
category.vue
product.vue
search.vue
basket.vue
account/
layout.vue
login.vue
register.vue
global/
notfound.vue
common/
cc-header.vue
cc-footer.vue
路由、简单的 app.vue 和特定布局文件的组合以及通用组件应该可以让您到达您想去的地方。
我使用 router meta 找到了另一个解决方案。我只有几个组件需要另一种布局。
我在 src/router/index.js 中添加了一个 plainLayout 元键。
export default new Router({
mode: 'history',
linkExactActiveClass: 'app-head-menu--active',
routes: [
{
path: '/',
component: Features,
},
{
path: '/comics/:id',
component: Comic,
props: true,
},
{
path: '/comics/:comic_id/:chapter_index',
component: Chapter,
props: true,
meta: {
plainLayout: true,
},
},
],
});
然后使用 src/App.vue 中的 playLayout 有条件地渲染布局。
<template>
<div>
<div v-if="!$route.meta.plainLayout">
<div class="app-head">
</div>
<div class="app-content">
<router-view/>
</div>
</div>
<div v-if="$route.meta.plainLayout">
<router-view/>
</div>
</div>
</template>
<script>
export default {
name: 'app',
};
</script>
查看演示项目 here。
我通过布局路由我的应用程序。例如登录不需要结构,只需要登录组件,但其他页面需要页眉页脚等,所以这是我如何在路由中执行此操作的示例:
// application routes
'/secure': {
name: 'secure',
component: require('../components/layouts/default'),
subRoutes: {
'/home': {
name: 'home',
component: require('../components/home/index')
}
}
}
//- public routes
'/insecure': {
name: 'insecure',
component: require('../components/layouts/full-bleed'),
subRoutes: {
'/login': {
name: 'login',
component: require('../components/session/login')
}
}
}
这两个布局模板都有一个 router-view 标签,因此您可以根据应用程序的不同部分的需要构建布局。
我在 App.vue 上动态检查全局路由并使用它来确定需要显示的内容。
应用程序.vue
<template>
<div id="app">
<top :show="show" v-if="show.header"></top>
<main>
<router-view></router-view>
</main>
<bottom v-if="show.footer"></bottom>
</div>
</template>
<script>
export default {
mounted: function() {
if(window.location.hash == "#/" || window.location.hash.indexOf('route')) {
vm.show.header = true
vm.show.footer = true
vm.show.slideNav = true
}
}
watch: {
$route: function() {
// Control the Nav when the route changes
if(window.location.hash == "#/" || window.location.hash.indexOf('route')) {
vm.show.header = true
vm.show.footer = true
vm.show.slideNav = true
}
}
}
}
</script>
这样我也可以通过道具控制顶部和底部导航中显示的内容。
希望这可以帮助!
我不知道任何“推荐方式”,但我的应用程序结构如下:
App.vue
- 每个组件(页面)的顶部菜单栏(用户未通过身份验证时不呈现)和 <router-view></router-view>
所以每个页面都可以有完全不同的布局。
评论接受的答案
有点不同意这一点。有同样的问题,这个答案让我很困惑。基本上,当您有一个想要在应用程序中随处重复使用的组件(例如页脚、页眉)时,您可以将它保存在 App.vue
中。这是我的情况,我想在每一页都有页脚和页眉,发现这个答案让我走错了方向,但你可以做到,而且它确实有效,例如 App.vue
:
<template>
<div id="app">
<app-header />
<router-view />
<app-footer />
</div>
</template>
<script lang="ts">
// Imports related to Vue.js core.
import { Component, Vue } from "vue-property-decorator";
// Imports related with custom logic.
import FooterComponent from "@/components/Footer.vue";
import HeaderComponent from "@/components/Header.vue";
@Component({
components: {
"app-footer": FooterComponent,
"app-header": HeaderComponent
}
})
export default class App extends Vue {}
</script>
<style lang="scss" scoped>
</style>
Footer.vue
(位于 components/Footer.vue
):
<template>
<div>
<footer>
<div>© {{ year }} MyCompany</div>
</footer>
</div>
</template>
<script lang="ts">
// Imports related to Vue.js core.
import { Component, Vue } from "vue-property-decorator";
@Component({})
export default class FooterComponent extends Vue {
public year = new Date().getFullYear();
}
</script>
<style lang="scss" scoped>
</style>
Header.vue
(位于 components/Header.vue
):
<template>
<div>
<header>
<router-link to="/">Home</router-link>
<router-link to="/about">About</router-link>
<router-link to="/contact">Contact</router-link>
</header>
</div>
</template>
<script lang="ts">
// Imports related to Vue.js core.
import { Component, Vue } from "vue-property-decorator";
@Component({})
export default class HeaderComponent extends Vue {}
</script>
<style lang="scss" scoped>
</style>