ChatGPT解决这个技术问题 Extra ChatGPT

vuejs application with different layouts (e.g. login layout, page layout, signup etc.)

I generated a project using vue-cli. I see project has one App.vue which is kinda main layout of the app - if I'm not mistaken. Here I put my basic HTML layout and <router-view></router-view>. Now the issue is that I need completely different layout for login (different wrappers , body has different classes) but I can't change it since App.vue has template which is kinda "fixed" as a layout. How to approach this issue? Is there recommended way?

Should I create new component that represents layout so in that case my App.vue template would only have <router-view></router-view> and then LoginLayout.vue would be included into it?

For any curious person, here is a link with interesting options to manage layouts with vue: markus.oberlehner.net/blog/dynamic-vue-layout-components
a good solution is : levelup.gitconnected.com/…

N
Nathaniel Ford

I think I found a solution. The approach has App.vue containing only <router-view></router-view> and then including different components that represent layout (if needed, containing <router-view> and subroutes). I found a project using it in that way here.

I think it keeps things more clean and organised. IMHO, hiding all elements which define layout structure (all the divs) would be too messy - especially for bigger apps.


The child routes as shown in that linked project lays things out nicely!
I prefer this approach, because, all components' routing are in the router from CoPilot.
This approach is also described in Vue Router guide: Essentials - Nested Routes.
Named slots is what you are looking for: vuejs.org/v2/guide/components-slots.html#Named-Slots
This answer is really unclear actually. Surprised it has so many upvotes.
t
tony19

A nice solution for this is using slots

First create your "layout component"

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>

Then use it in another component:

<template>
  <layout-basic>
    <p>Hello world!</p>
  </layout-basic>
</template>

<script>
  import LayoutBasic from '@/components/layouts/basic'
  export default {
    components: {
      LayoutBasic
    }
  }
</script>

"Hello world" will appear where the <slot/> tag is.

You can also have multiple slots with names, see the complete docs.


I think this is the best approach, since it's exactly what slots are intended for.
Perfect. No need to use 3rd party library/component.
This is easy to manage, but can cause some problems: 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. from here
This is the correct answer. Check out the Named Slots: vuejs.org/v2/guide/components-slots.html#Named-Slots
C
Chad Carter

Utilizing Routes, and in particular, children routes is a great way to approach having common layouts in Vue.

All of this code is utilizing Vue 2.x

Start by having a really simple vue component called App that has no layout.

app.vue

<template>
    <router-view></router-view>
</template>

Then have a Routes file that you'll bring into your Vue instance.

Routes.(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 }    
        ]    
    }        
    ]
})

The code is using lazy loading (with webpack) so don't let the () => import(...) throw you. It could have just been import(...) if you wanted eager loading.

The important bit is the children routes. So we set the main path of /account to utilize the /components/account/layout.vue but then the very first two children specify the main content vue (Login). I chose to do it this way because if someone just browses to /account I want to greet them with the login screen. It may be appropriate for your app that /account would be a landing page where they could check the order history, change passwords, etc...

I did the same thing for catalog... / and /catalog both load the catalog/layout with the /catalog/catalog file.

Also notice that if you don't like the idea of having "subfolders" (i.e. account/login instead of just /login) then you can have aliases as I show in the login.

By adding , alias: '/login' it means users can browse to /login even though the actual route is /account/login.

That is the key to the whole thing, but just to try and make the example complete...

Here is my boot file which hooks up my app.vue and routes:

boot.(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)
});

I created a layout.vue file for each of my main sections of my app (account, catalog, etc).

account/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>

And the layout for catalog...

catalog/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>

Both layouts use common components like header and footer, but they don't need to. The catalog layout has categories in the side nav, while the account layout doesn't. I put my common components under components/common.

common/footer.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>

Overall file structure

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               

The combination of routes, a plain app.vue, and specific layout files, along with common components should get you to where you want to be.


l
lingceng

I find another solution by using router meta. I just have a few components need another layout.

I added a plainLayout meta key in src/router/index.js.

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,
      },
    },
  ],
});

Then render layout conditionally with playLayout in src/App.vue.

<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>

See a demo project here.


Your approach, combined with @user2343398's approach does the perfect job.
T
Tremendus Apps

I route my apps through a layout. Eg login requires no structure, just the login component, but other pages require, header footer etc, so here is an example of how I do this in my routes:

// 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')
    }
  }
}

Both of these layout templates have a router-view tag, so you can them build your layouts as you require for different parts of the app.


Can you provide an example for this?
C
Community

I dynamically check the route globally on App.vue and use that to determine what needs to be shown.

App.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>

That way I'm also able to control what's shown in the top and bottom navs through props.

Hope this helps!


This might be useful in small cases but can get pretty messy and prone to errors.
l
lukpep

I don't know about any "recommended way" but my app is structured like this:

App.vue - just top menu bar (which is not rendered when user is not authenticated) and <router-view></router-view> for each component (page)

So every page could have totally different layouts.


It's not so simple , template I have is completely different in structure for login or lets say dashboard - not just show hide few elements.
and it could be. Whole html content could be different
D
Daniel Danielecki

Comment to the accepted answer

Kind of disagree with this. Had the same issue and this answer confused me. Basically when you have a component which you'd like to reuse everywhere (e.g. footer, header) in your application then you can keep it in the App.vue. It was my case, I wanted to have footer and header in every page, finding this answer put me into the wrong direction, but you can do it and it does works, for example 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 (located in components/Footer.vue):

<template>
  <div>
    <footer>
      <div>&copy; {{ 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 (located in 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>

关注公众号,不定期副业成功案例分享
Follow WeChat

Success story sharing

Want to stay one step ahead of the latest teleworks?

Subscribe Now