Goal: Make a navigation tabbar

An analysis of.

Our goal is to make a navigation tabbar, request

  1. This navigation can be used not only on one page, but also across multiple pages
  2. The style of each page may be different
  3. Each page icon, text may be different
  4. The number of navigations per page may vary

To achieve this, we need to disassemble the function:

  1. Extract a generic tabBar and then define slots in it to put tabbarItems as needed,
  2. TabBarItem contains images, text. Images and text are also slots, and different tabbarItems may display different images with different text.
  3. The tabBarItem data structure needs to be defined as a JSON that specifies the url to jump to

Two. Framework implementation

1. How do we usually achieve this?

Step 1: Define an HTML section in app. vue with the id of the outer div as tabBar and the class attribute of the inner div as tabBarItem.

<template>
  <div id="app">
    <div id="tabBar">
      <div class="tabBarItem">Home page</div>
      <div class="tabBarItem">classification</div>
      <div class="tabBarItem">The shopping cart</div>
      <div class="tabBarItem">my</div>
    </div>
  </div>
</template>
Copy the code

Step 2: Define the body style.

In general, the body style is defined separately in the main.css file. Put it in assets

body {
  margin: 0px;
  padding: 0px;
}
Copy the code

Having defined the main.css file, we need to import it into the app.vue file

<style>
  @import "./assets/main.css";
</style>
Copy the code

Import CSS file styles using @import ‘file path’ and import JS files using import ‘file path’

Step 3: Define the tabBar style

TabBar uses Flex Flex layout.

    #tabBar {
        display: flex;
    }

    .tabBarItem {
        flex: 1;
        text-align: center;
      }
Copy the code

This code specifies the layout used by tabBar. It has an elastic layout based on the number of child elements. In the child elements we set flex: 1 for each element to mean that the space is allocated equally as it is available.

Step 4: Define additional styles

<style>
  @import "./assets/main.css";

  #tabBar {
    display: flex;
    background-color: #e6e6e6;

    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;

    box-shadow: 0 -3px 1px darkgrey;
  }

  .tabBarItem {
    flex: 1;
    text-align: center;
  }
</style>
Copy the code

In addition to layout styles, there are bottom alignment, navigation shadows and so on.

Summary: This navigation is not generic, if we want to reuse, we need to copy the HTML content, and also copy the CSS style. We can take the common part as a component

2. Abstract the tabBarItem component

Step 1: Create a new component tabBarItem in Components

This is a simple extraction that takes the functionality we just put in app.vue and extracts it into a single component

<template>
  <div id="tabBar">
    <div class="tabBarItem">Home page</div>
    <div class="tabBarItem">classification</div>
    <div class="tabBarItem">The shopping cart</div>
    <div class="tabBarItem">my</div>
  </div>
</template>

<script>
    export default {
        name: "tabBarItem"
    }
</script>

<style scoped>
  #tabBar {
    display: flex;
    background-color: #e6e6e6;

    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;

    box-shadow: 0 -3px 1px darkgrey;
  }

  .tabBarItem {
    flex: 1;
    text-align: center;
  }
</style>
Copy the code

Then, introduce the components in app.vue

<script>
  import TabBar from "./components/TabBar"
  export default {
    name: 'App'.components: {
      TabBar
    }
  }
</script>
Copy the code

In Vue, you can call a component using its abbreviation, as follows:

<div id="app">
      <tab-bar></tab-bar>
  </div>
Copy the code

This makes tabBarItem even more reusable.

3. Improve the tabBarItem component

We know that tabBar has text as well as pictures. When we mouse click, there are corresponding pictures or mosquito style changes. Now let’s do that, change the image and the text. Step 1: Place two images in the tabBarItem, one unclicked and one clicked. Bring your own pictures. Any pictures

<div class="tabBarItem">
    <slot name="item-pic"></slot>
    <slot name="item-pic-active"></slot>
    <div ><slot name="item-name"></slot></div>
  </div>
Copy the code

Add a slot to store the second image. Pass two images in the call

<tab-bar-item>
      <img slot="item-pic" src=".. /.. /assets/img/tabBar/1.jpeg">
      <img slot="item-pic-active" src=".. /.. /assets/img/tabBar/4.jpeg">
      <div slot="item-name">Home page</div>
</tab-bar-item>

Copy the code

Here you pass in two images and specify the slot position for each image

Then let’s look at the results:

It worked. It worked. But we want: mouse not click, display figure 1; Click the mouse and figure 2 will appear. This is easy to implement, using an isActive variable to modify the TabBarItem component

<template>
  <div class="tabBarItem">
    <div v-if=! "" isActive"><slot name="item-pic"></slot></div>
    <div v-else><slot name="item-pic-active"></slot></div>
    <div><slot name="item-name"></slot></div>
  </div>
</template>

<script>
    export default {
        name: "TabBarItem".data() {
          return {
            isActive: false}}}</script>
Copy the code

Define a variable isActive in the component script, and then use V-if on the slot to do the trick. Notice how v-if and v-else are written.

We have a convention here that we usually don’t write v-if or V-else inside the slot, because that will be replaced later. So, wrap a div around it

Let’s look at the effect: When we set isActive:false, it looks like this

When we set isActive:true, the effect is as follows:

As you can see, I’ve just switched the order of the four images. The specific picture is not important, the important thing is that the effect is good.

Step 2: Change color when text is active.

This is even easier.

<template>
  <div class="tabBarItem">
    <div v-if=! "" isActive"><slot name="item-pic"></slot></div>
    <div v-else><slot name="item-pic-active"></slot></div>
    <div v-bind:class="{active:isActive}"><slot name="item-name"></slot></div>
  </div>
</template>

<style scoped>..active {
    color: red; }...</style>
Copy the code

Bind directly to a class style, and when the text isActive (isActive:true), the text is red to see the effect:

This completes the tabBarItem wrapper

Realization of navigation routing function

Now that the tabBar navigation is complete, we click on the home page, which should show the home page component content. Clicking on the category should display the category component content. Let’s implement this part of the function. This is the routing function of navigation.

The first step is to install the routing component

npm install vue-router --save
Copy the code

Vue-router is a runtime dependency, so the –save parameter is required

Step 2: Create the Router folder and create the index.js file

// Step 1: import vue and vue-router packages
import Vue from 'vue'
import Router from 'vue-router'
// Step 2: Install vue-Router plug-in
Vue.user(Router)
// Step 3: Create Router components
const route = [{

}]

const vueRouter = new Router({
    route: route
})
// Step 4: Export Route component
export default vueRouter

Copy the code

Step 3: Introduce vueRouter in the main.js file

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

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  render: h= > h(App)
})
Copy the code

Step 4: Plan the project structure

In general, we have all the common component modules under the Components directory. For page-related modules, we will create a separate folder, the folder name can be views or Pages or other folders, business related pages are stored in this folder. Our project directory structure is as follows:

We created 4 directories under views to store the routing content of each navigation component.

Step 5: Add a route

Next, configure the route for the navigation

// Import the component first
const Home = () = > import('.. /views/home/Home')
const Cart = () = > import('.. /views/cart/Cart')
const Category = () = > import('.. /views/category/category')
const Profile = () = > import('.. /views/profile/profile')

// Then configure the route
const routes = [{
  path: "".redirect: "/home"}, {path: "/home".components: Home
},{
  path: "/category".components: Category
},{
  path: "/cart".components: Cart
},{
  path: "/profile".components: Profile
}]
const router = new VueRouter({
  routes
})
Copy the code

Four routes are configured to route to corresponding components. When an empty route is configured, the /home route is located. Now that the route is configured, click events are configured for the button. Our tabBarItem component wraps like this

<template>
  <div class="tabBarItem" v-on:click="clickItem">
    <div v-if=! "" isActive"><slot name="item-pic"></slot></div>
    <div v-else><slot name="item-pic-active"></slot></div>
    <div v-bind:class="{active:isActive}"><slot name="item-name"></slot></div>
  </div>
</template>
Copy the code

We add a click event at the component level, but the jump url is not fixed, and we pass it in as a parameter.

v-on:click="clickItem"
Copy the code

Data is passed between components using the props property

<script>
    export default {
        name: "TabBarItem".props: ["path"].data() {
          return {
            isActive: false}},methods: {
          clickItem() {
            this.$router.push(path);
          }
        }
    }
</script>
Copy the code

Define a props property in the component to receive using an itemUrl. Add the parameter item-URL =”/home” to the TabBar. The other three components are called the same way.

    <tab-bar-item  path="/home" >
      <img slot="item-pic" src=".. /.. /assets/img/tabBar/1.jpeg">
      <img slot="item-pic-active" src=".. /.. /assets/img/tabBar/4.jpeg">
      <div slot="item-name">Home page</div>
    </tab-bar-item>
Copy the code

Finally, add a component display area in app.vue

<template>
  <div id="app">
    <router-view></router-view>
    <tab-bar></tab-bar>
  </div>
</template>
Copy the code

Let’s see what happens

Step 6: Style the checked/unchecked buttons

<script>
    export default {
        name: "TabBarItem".props: ["path"].computed: {
          isActive(){
            return this.$route.path.indexOf(this.path) ! = = -1}},data() {
          return {
           // isActive: false}},methods: {
          clickItem() {
            this.$router.push(this.path); }}}</script>
Copy the code

Added a calculation attribute. If the route has the same path as the current forward route, the route is selected. Otherwise, the route is not selected. The effect is as follows:

Step 7: Extract the navigation text style

Now, we set the text to be red when navigation is active, but… Not all navigation is red when activated, so we need to set it dynamically. That is, a parameter is passed from the caller through the component. As follows:

<tab-bar-item path="/home" activeStyle="blue"> <img slot="item-pic" src=".. /.. /assets/img/tabBar/1.jpeg"> <img slot="item-pic-active" src=".. /.. 4 / assets/img/tabBar/jpeg "> < div slot =" item - the name "> home page < / div > < / TAB bar - item >Copy the code

Add a property activeStyle=”blue”. Accordingly, we need to add a prop property where the component definition is located

props: {
  path: String,
  activeStyle: {
    type: String,
    default: 'red'
  }
},
Copy the code

Add the activeStyle style to the Prop property and set the default style red.

computed: { isActive(){ return this.$route.path.indexOf(this.path) ! == -1 }, isActiveStyle() { return this.isActive ? {color: this.activeStyle}:{} } },Copy the code

Add a property isActiveStyle to the calculated properties. If the navigation is active, the style will be displayed. Otherwise, there is no style to see the effect.

Step 8: Further componentize

Let’s take a look at the app.vue file

<template>
  <div id="app">
    <router-view></router-view>
    <tab-bar>
      <tab-bar-item  path="/home" activeStyle="blue">
        <img slot="item-pic" src="./assets/img/tabBar/1.jpeg">
        <img slot="item-pic-active" src="./assets/img/tabBar/4.jpeg">
        <div slot="item-name">Home page</div>
      </tab-bar-item>
      <tab-bar-item path="/category" activeStyle="green">
        <img slot="item-pic" src="./assets/img/tabBar/2.jpeg">
        <img slot="item-pic-active" src="./assets/img/tabBar/3.jpeg">
        <div slot="item-name">classification</div>
      </tab-bar-item>
      <tab-bar-item path="/cart" activeStyle="pink">
        <img slot="item-pic" src="./assets/img/tabBar/3.jpeg">
        <img slot="item-pic-active" src="./assets/img/tabBar/2.jpeg">
        <div slot="item-name">The shopping cart</div>
      </tab-bar-item>
      <tab-bar-item path="/profile" activeStyle="purple">
        <img slot="item-pic" src="./assets/img/tabBar/4.jpeg">
        <img slot="item-pic-active" src="./assets/img/tabBar/1.jpeg">
        <div slot="item-name">my</div>
      </tab-bar-item>
    </tab-bar>
  </div>
</template>

<script>
  import TabBar from "./components/tabBar/TabBar"
  import TabBarItem from "./components/tabBar/TabBarItem";
  export default {
    name: 'App'.components: {
      TabBar,
      TabBarItem
    }
  }
</script>

<style>
  @import "./assets/main.css";
</style>

Copy the code

In the template part, there is a lot of content, usually the content of app. vue is very concise, so we can also abstract this part of the component to extract the file to MainTabBar, after the extraction pay attention to the image file and the path of the Vue component

<template>
  <tab-bar>
    <tab-bar-item  path="/home" activeStyle="blue">
      <img slot="item-pic" src=".. /.. /assets/img/tabBar/1.jpeg">
      <img slot="item-pic-active" src=".. /.. /assets/img/tabBar/4.jpeg">
      <div slot="item-name">Home page</div>
    </tab-bar-item>
    <tab-bar-item path="/category" activeStyle="green">
      <img slot="item-pic" src=".. /.. /assets/img/tabBar/2.jpeg">
      <img slot="item-pic-active" src=".. /.. /assets/img/tabBar/3.jpeg">
      <div slot="item-name">classification</div>
    </tab-bar-item>
    <tab-bar-item path="/cart" activeStyle="pink">
      <img slot="item-pic" src=".. /.. /assets/img/tabBar/3.jpeg">
      <img slot="item-pic-active" src=".. /.. /assets/img/tabBar/2.jpeg">
      <div slot="item-name">The shopping cart</div>
    </tab-bar-item>
    <tab-bar-item path="/profile" activeStyle="purple">
      <img slot="item-pic" src=".. /.. /assets/img/tabBar/4.jpeg">
      <img slot="item-pic-active" src=".. /.. /assets/img/tabBar/1.jpeg">
      <div slot="item-name">my</div>
    </tab-bar-item>
  </tab-bar>
</template>

<script>
  import TabBar from "./TabBar";
  import TabBarItem from "./TabBarItem";
    export default {
        name: "MainTabBar".components: {
          TabBar,
          TabBarItem
        }
    }
</script>

<style scoped>

</style>

Copy the code

Then, MainTabBar is introduced in app.vue

<template>
  <div id="app">
    <router-view></router-view>
    <main-tab-bar></main-tab-bar>
  </div>
</template>
Copy the code

The effect is the same as before

Alias the file

When we have a lot of paths, as we did when we abstracted the components above, we find that the file location changes, and many paths change with it. In VUE, you can alias the path so that you don’t have to change the path after you change the file location. In the build/webpack base. Conf. Js file, there was a resolve option

  resolve: {
    extensions: ['.js', '.vue', '.json'],
    alias: {
      '@': resolve('src'),
    }
  },
Copy the code
  • Extensions: Indicates that you can omit the extension name when importing a path
  • Alias: Indicates an alias for the path. Resolve (‘ SRC ‘) indicates an alias for the SRC path.

This way, we can give the other folders an alias as well.

  resolve: {
    extensions: ['.js'.'.vue'.'.json'].alias: {
      The '@': resolve('src'),
      'components': resolve('@/components'),
      'assets': resolve('@/assets'),
      'router': resolve('@/router'),
      'views': resolve('@/views')}},Copy the code

When given an alias, such as SRC /components, the path can use @/components. For subsequent files that use this path, just use @/components

In use, it is also divided into several scenarios

  1. Import the path in the component using import
  2. No import, such as image path
  3. Import path in routing navigation

1. Import components using import

Before we introduced MainTabBar in app.vue, we introduced the script like this:

import MainTabBar from "./components/tabBar/MainTabBar";
Copy the code

Now that we have configured the path alias, we can omit the./ and start with components

import MainTabBar from "components/tabBar/MainTabBar";
Copy the code

The same is true for style style references

<style>
  @import "assets/main.css";
</style>
Copy the code

Let’s just go ahead and omit the dot/PI.

2. Introduce images and other non-imports

For example, when we set the navigation icon in the maintabbar. vue component, there is a lot of SRC, which is what we wrote before

    <tab-bar-item path="/profile" activeStyle="purple"> <img slot="item-pic" src=".. /.. /assets/img/tabBar/4.jpeg"> <img slot="item-pic-active" src=".. /.. / assets/img/tabBar / 1. Jpeg "> < div slot =" item - the name "> I < / div >Copy the code

After the route alias is defined, we can write it as follows:

<tab-bar-item  path="/home" activeStyle="blue">
      <img slot="item-pic" src="~assets/img/tabBar/1.jpeg">
      <img slot="item-pic-active" src="~assets/img/tabBar/4.jpeg">
      <div slot="item-name">Home page</div>
    </tab-bar-item>
Copy the code

The alias assets is used, but must be preceded by a ~.

3. The path in a route

So far, I’ve found that by introducing components into the route, you can’t use aliases, but you can use the @ symbol to represent SRC

//const Home = () => import('@/views/home/Home')
import Home from '@/views/home/Home';
const Cart = () = > import('@/views/cart/Cart')
const Category = () = > import('@/views/category/category')
const Profile = () = > import('@/views/profile/profile')
Copy the code

Once the alias is used, an error is reported. No matter it’s here or not. Still need to explore

That’s also a problem

Note: after we modify the webpack.base.conf.js configuration file, we need to restart the service. Otherwise it will not take effect