The second encapsulation of the 2-Home page of the Vue e e-commerce project notes in Silicon Valley, the interface request function used by axios and the encapsulation used by vuex, Mock and Swiper

Posted by TheMoose on Wed, 23 Feb 2022 04:11:34 +0100

The second encapsulation of the 2-Home page of the Vue e e-commerce project notes in Silicon Valley, the interface request function used by axios and the encapsulation used by vuex, Mock and Swiper

Tutorial address

Split non routing components in Home components

I only split TypeNav, ListContainer and Floor components here, because these three components are dynamic data

  1. TypeNav components need to be used in both Home and Search components. If a non routing component is used by multiple components, it is defined in the components directory and globally registered in the entry file
  2. ListContainer and Floor components are defined in the Home directory because they are used by the Home component
  3. When splitting a static page into components, pay attention to the path of the picture
// 1. Global registration of global components. The first parameter is a string. Components are used The name attribute can get the name attribute of the component instance
import TypeNav from '@/components/TypeNav'
Vue.component(TypeNav.name,TypeNav)
// 2. Non global components are registered with components
<template>
  <div>
    <TypeNav></TypeNav>
    <ListContainer></ListContainer>
    <Floor></Floor>
    <Floor></Floor>
  </div>
</template>

<script>
import Floor from "./Floor";
import ListContainer from "./ListContainer";
export default {
  name: "Home",
  // Registration of non global components
  components: { Floor, ListContainer },
};
</script>

Send ajax request to get data

postman test interface

Review the passing of postman parameters:

  1. The querystring parameter can be spliced directly after the url. The parameter will automatically fill in the key value in the query params tab (so it can be filled in this tab)
  2. params parameter can be spliced directly after the url. There is no other way
  3. Request body urlencoded parameter, select the body tab, and select x-wwww-from-urlencoded
  4. json format request body parameter, select raw (original format) to write the content in json format

The secondary encapsulation of axios and the use of nprogress

As long as the secondary encapsulation of axios is performed, it will not be encapsulated on the original axios. Create a new instance of axios for encapsulation. What needs to be done for encapsulation:

  1. Configure base path and timeout
  2. Use nprogress to add a progress bar for sending ajax requests, using steps
    • Install and introduce js and css styles. css style belongs to global style and needs to be used on the page, so it can be used in main Introducing styles into js
    • Open progress bar in request Interceptor: nprogress start()
    • Whether the response interceptor fails or succeeds, it needs to stop the progress bar: nprogress done()
  3. The return response does not require response Data to obtain data, but the content of direct response is data
  4. Unified processing of error requests

axios and nprogress need to be installed:

  1. npm i axios
  2. npm i nprogress

Create an api file in the src directory, which creates Ajax JS file for secondary encapsulation of axios:

import axios from "axios"
import nprogress from "nprogress"
// import 'nprogress/nprogress.css' is introduced in the entry file
// Custom configuration creates an instance with the same functions as axios
const server = axios.create({
  // /api/product/getBaseCategoryList
  // Because api interfaces are prefixed with api, the base path is specified here
  baseURL: '/api',
  timeout: 5000
})
// If you need to add additional functions to axios or add additional information to the request header, you must use the request interceptor and response interceptor
server.interceptors.request.use((config) => { // The request interceptor also has a failed callback, but it is generally useless, because if it fails, there is no following
  // Add additional functions or special request headers. Here, execute the start of nprogress
  nprogress.start()
  return config // Return the configuration object, otherwise the request cannot be actually issued
})
server.interceptors.response.use(
  (response) => {
    nprogress.done()
    // We only need the data in the response, so we directly return the data
    return response.data
  },
  (error) => {
    nprogress.done() // Stop the progress bar regardless of success or failure
    alert('send out ajax request was aborted' + error.message || 'unknown error')
    // If a promise instance with a failed status is returned, the failed callback function will continue to be used if the request fails, or catch can be used to handle the error
    // return Promise.reject(new Error('failed to send ajax request '))
    // If a promise instance without status is returned, the promise chain will be interrupted. Even if the AIX OS request specifies a failed callback, it will not be executed. The error processing has been handed over to the response interceptor
    return new Promise(() => {})
  }
)
// Expose the axios instance object we created before using it
export default server

Search node_ Use of modules plug-in

This plug-in can help us search node quickly_ Packages in modules:

  1. After installation, use the ctrl+shift+p shortcut key to call up the search window and select the installed Search node_modules
  2. After using the plug-in once, type ctrl+k and ctrl+n to directly enter the Search node_modules search for packages to find
  3. For example, search nprogress and press enter to see the structure in the package. Let's check the package The main configuration in the JSON file is "main": "nprogress.js"
  4. This is why we only need a directory when introducing third-party packages. If CSS is introduced, we need to write the complete structure of the package directory + file name: import 'nprogress / nprogress css'

Interface request function

Create a new index. In the api directory JS file, which writes functions, called request interface function file
Each interface corresponds to a function. If you need to obtain the data returned by the relevant interface, you only need to call the encapsulated corresponding method

Three parameters carried by axios sending request:

  1. params parameter is carried in the url and is part of the path
  2. The query parameter can be carried in the url or configured using params in the configuration object
  3. The request body parameter is configured in the configuration object using data. If the request body parameter is a urlencoded write string, the object form is used if it is in json format
// Introduce our own packaged axios
import request from './ajax'
// Using the separate exposure method, we only need to call this method when obtaining data, and this method returns a promise instance
// Because we use the request sent by axios encapsulated by ourselves and have handled the error by default, we only need to specify the successful callback, and the callback response is the returned data
// Or directly use awiat to obtain the successful value, that is, the returned data
// Get the request interface of three-level classification
export const reqCategoryList=()=>{
  return request({
    // The requested path. Because the baseURL is configured, there is no api here
    url:'/product/getBaseCategoryList',
    method:'get'
  })
}
// Call test
reqCategoryList()

Test of request interface function:

  1. The first way: directly in index The JS file calls the wrapped request interface function, but it needs to be executed, so it needs mian.. The file is introduced into the JS entry file, which only needs to be executed without fromimport '@/api'
  2. The second way: because the interface function file exposes the method, it can be found in Mian JS entry file, and then execute the method

Problems found after test:
When 404 is returned, the data cannot be obtained, and the axios encapsulated by us is executed. For the error handling method, a modal box pops up. Observe the network request and find that the address of the request is: http://localhost:8080/api/product/getBaseCategoryList
That is, find the local resources from us, that is, the api under the public directory... So you need to configure the proxy server

In Vue config. JS file to configure the agent:
Open the official website of webpack and select the configuration. The DevServer on the left is the reference about the configuration. You can search proxy directly
Official website reference

vue. config. Specific configuration of JS file: restart the project after modifying the configuration file

module.exports = {
  lintOnSave: false,
  devServer: {
    proxy: {
      '/api': {
        // Server address forwarded by proxy
        target: 'http://39.98.123.211',
        // When requesting resources, we don't need to remove the api marked as proxy, because the requested resource itself has api
        // pathRewrite: { '^/api': '' },
      },
    },
  },
}

After configuring the agent, the data is returned successfully, and the progress bar of nprogress is displayed normally

Use of vuex

vuex is also a plug-in used to store data. The steps used are the same as Vue router

  1. Installing vuex:npm i vuex@3
  2. Using vuex: Vue use(Vuex)
  3. Exposure: export default vuex Store()
  4. In main JS and configured in the configuration item:

Five core concepts of vuex and modularization

  1. state: as a warehouse for storing data
  2. Changes: store data directly
  3. actions: execute asynchronous tasks and submit them to changes
  4. getters: equivalent to calculating attributes, which can simplify obtaining data in state
  5. modules: for modular development of vuex
    • When acquiring data in state after modularization, you need to specify the module name: this$ store. home. list
    • However, it is not necessary to obtain the contents of getters or action s and changes

Create the store directory under the src directory and create the index JS as the total stroe, create home JS and search JS for modular development
home.js and search JS module file:

// home module of store
const state = {}
const mutations = {}
const actions = {}
const getters = {}
export default {
  state, mutations, actions, getters
}

index.js total store file:

import Vue from "vue"
import Vuex from 'vuex'
Vue.use(Vuex)
import home from "./home"
import search from "./search"
const state = {}
const mutations = {}
const actions = {}
const getters = {}
export default new Vuex.Store({
  state, mutations, actions, getters,
  // Modular development of vuex
  modules: { home, search }
})

Write vuex request to store data

Because the data obtained belongs to the Home component, it is in the Home.com under the store JS to write the vuex file:

// Import interface request function
import { reqCategoryList } from "@/api"
const state = {
  // The data type depends on the data type returned by the request
  categoryList : []
}
const mutations = {
  // Two parameters. The first parameter is state and the second parameter is the data to be stored
  RECEIVE_CATEGORYLIST(state,categoryList){
    state.categoryList = categoryList
  }
}
const actions = {
  async getCategoryList({commit}){ // The parameter here is the context object, which contains some methods of $store. We only need to commit, so the structure is assigned
    // Call the interface request function to send the request, and return a promise instance. Use awati to receive the successful value of the promise instance, that is, the returned result
    // Synchronous code realizes asynchronous effect and good readability
    const result = await reqCategoryList() // Here, you have to wait for the result to return before you go back to execute the following code
    if(result.code === 200){
      commit('RECEIVE_CATEGORYLIST',result.data) // Although axios is processed and data is returned, the data in data is the data returned by the server
    }
  }
}
const getters = {}
// What is directly exposed is an object
export default {
  state, mutations, actions, getters
}

The dispatch request obtains the data, and uses the calculation attribute to store the array on the component instance

Because this data is the data required by TypeNav, operations are performed in this component

  1. Send a request in mounted() to store the data in vuex. There is data in vuex through the developer tool
  2. In computed, mapState is used to store data on the component instance itself in the way of calculating attributes, which is very convenient in use. Through developer tools, TypeNav components have attributes
<script>
import {mapState} from 'vuex'
export default {
  name: "TypeNav",
  // After the component is mounted, it will be executed immediately. Send a request to obtain the data, store it in vuex, and you can see the data in vuex
  mounted() {
    this.$store.dispatch('getCategoryList')
  },
  // In the future, as long as you get data from vuex, state and getters will operate in the calculation properties. If you get actions and mutions, you will operate in mtehods
  computed:{
    // The mapState function can be an array or an object. The conditions to be met when using the array
    // 1. Data directly refers to the data in the general state, but we are developing it in modules and do not define it in the general store
    // 2. The name in the array must be consistent with the data name in the state
    // 3. The object form parameter is a callback function, and the parameter is state
    ...mapState({categoryList:state=>state.home.categoryList})
  }
};
</script>

TypeNav three-level classification

Dynamic display using the obtained data

Use v-for to traverse and display the navigation data. When using: key, pay attention to view the returned data and use the id provided by the returned data

Move the mouse in, remove the display background and secondary menu, and hide the secondary menu

The previous implementation method was implemented using: hover, that is, pure css

/* &Represents the parent element. Previously, here is: hover. You can control the addition of background color and the display and hiding of secondary menu by moving the mouse in and out. Now change it to active is implemented by vue */
&.active {
  background-color: #e1251b;
  .item-list {
    display: block;
  }
}

Implemented using js:

  1. Remove the original hover and change it to a class
  2. Force binding this class actvie on item
  3. Set a data to dynamically control whether the class takes effect on the item through the data (move in the item, and the item will add the class)
  4. Note: when moving out, the mouse moves into all commodity classifications, and the secondary menu is not hidden. That is to say, all commodity classifications also need a move out event to control the secondary menu. Here, event delegation is used
    • Therefore, you need to use a div to wrap all product categories and navigation together, and bind the move out event to the Div
    • Because the layout here adopts positioning, moving the order will not change the structure of the page
// The move out event is bound to the parent
<div @mouseleave="currentIndex = -1">
  <h2 class="all">All commodity categories</h2>
  <div class="sort">
    <div class="all-sort-list2">
      <!-- If yes true Then class take effect(Class is fixed,However, whether it takes effect or not is determined by conditions) -->
      <div
        class="item"
        @mouseenter="currentIndex = index"  
        :class="{ active: index === currentIndex }"
        v-for="(c1, index) in categoryList"
        :key="c1.categoryId"
      >
      </div>
    </div>
  </div>
</div>
// Simulated data using data
data() {
  return {
    // Start specifying a value that is not the same as any item index
    currentIndex: -1,
  };
},

Anti shake and throttling

Demonstrate the Caton phenomenon of browser

Change the previous mouse out to the form of callback function:

methods:{
  // Receive the passed index
  moveItem(index){
    this.currentIndex = index
    // If the mouse scrolls too fast, these callback functions will not be triggered after all the moves in. This is a problem for the browser. If the specified callback has a lot of calculations, it will cause the browser's Caton phenomenon
    console.log(index)
  }
},

Concept of anti shake and throttling:

  1. Normal: the event triggers are very frequent, and the callback function needs to be executed every time. If the trigger events are very frequent and there are a lot of calculations inside the function, the Caton phenomenon is likely to occur
  2. Anti shake: all previous triggered callbacks will be cancelled, and the last execution will be executed after the specified time, that is, triggered several times in succession, and only the last execution will be executed
  3. Throttling: callback will not be triggered frequently within the specified time interval. Callback will only be triggered if it is greater than this time interval. Frequent triggering will become a small number of triggers

Using lodash

  1. Installation: there is no need to install, because there are some dependencies on lodash when using scaffolding to create projects
  2. introduce:
    • Import all: the entire lodash will be imported, which will increase the packaging volume: Import_ from 'lodash'
    • On demand import: through Search node_modules can see that lodash exposes each function separately, so we use that function to introduce that function: import throttle from 'lodash/throttle'
  3. The callback can only be triggered once when the configuration is set to true, so it can only be triggered once in 50 seconds, which is the default time for the callback to take effect
    • trailing: whether to execute the function after the time interval. The default value is true
    • leading: whether to execute the function before the time interval. The default value is false
  4. Two functions:
    • _. debounce is the anti shake function
    • _. Throttle is a throttle function
methods:{
  // Note that the abbreviation in the object cannot be used when defining the method, and the function cannot use the arrow function, because the function in it is a function managed by vue, and the arrow function leads to this pointing error
  // And if the global import method is adopted, use throttle
  moveItem:throttle(function(index){
    this.currentIndex = index
    console.log(index)
  },50,{trailing:false})
},

The three-level classification jumps to the Search routing component

An imperfect way

  1. Use the declarative navigation < router link > tag to jump. The component tag corresponds to the instance object of a component. The three-level for loop is used in the three-level classification, which will generate a large number of component instance objects in memory
  2. Use programmatic navigation this$ rotuer. The jump of push will bind a large number of event callback functions, and there will be a pile of functions in memory. The efficiency is still low
    • As long as [] appears to the right of the = sign, a new array is generated in memory
    • As long as {} appears to the right of the = sign, a new object is generated in memory
    • Only function() appears to the right of the = sign, which means that a new function has been generated in memory

Perfect way

Problems to be solved when using event delegation and programmatic navigation:

  1. How do you know if the a tag is clicked
  2. Suppose you know that you clicked on the a tag, how do you know whether you clicked on the primary navigation or the secondary navigation
  3. How to carry parameters

Principle of event delegation:

  1. The event is added to the common ancestor element of all required event elements. It is required that the ancestor element must be unique
  2. Because item already has v-for, which is not unique, it cannot be directly added to item

Event object event:

  1. Every time an event is triggered, the browser (browser kernel) will encapsulate all the information related to the trigger event into an object
  2. When the browser calls the callback function, the first formal parameter automatically passed to the callback function
    • Callback function: we define it. We don't call it, but it executes automatically
  3. In vue, if the callback function does not pass any parameters, a $event is passed by default

Use custom attributes:

  1. Adding the attribute beginning with data - to the tag is a custom attribute
  2. When obtaining, the event. Object is passed through the tag element target. Data set to get the attribute object composed of data -
    • Note: the attribute values obtained by this method are in lowercase

be careful:

  1. Get the values of custom attributes, which are all lowercase
  2. When passing query parameters, the key is determined according to the parameters required by the server, so don't forget to capitalize

The merging of query parameters and params parameters

Question:

  1. The three-level navigation jump to the Search component will pass the past query parameters, and clicking the Search button to jump to the Search component will carry the params parameters
  2. You can view the parameters through $route in the vue developer tool. For example, if I search by searching keywords, the params parameter has content, but at this time, the previous params parameter disappears and is replaced by the passed query parameter
  3. You need to carry both parameters to the Search component when searching, so that you can Search according to a variety of conditions

Use event delegation to pass parameters, jump to the Search component, and merge the query and params parameter codes:

// Add custom attributes to all a Tags
<a href="javascript:;" :data-categoryName = "c1.categoryName" :data-category1Id = "c1.categoryId">{{ c1.categoryName }}</a>
<a href="javascript:;" :data-categoryName = "c2.categoryName" :data-category2Id = "c2.categoryId">{{ c2.categoryName }}</a>
<a href="javascript:;" :data-categoryName = "c3.categoryName" :data-category3Id = "c3.categoryId">{{ c3.categoryName }}</a>
// Callback function specified by parent element
toSearch({target}){ // Deconstruction assignment directly obtains the element that triggers the event
  const {categoryname,category1id,category2id,category3id} = target.dataset   
  if(categoryname){
    const location = {name:'search'}
    // When setting parameters here, be sure to pay attention to the case of key s. These attributes, as parameters, are sent to the server to obtain data according to the data required by the server
    const query = {categoryName:categoryname}
    // Click the first level menu
    if(category1id){
      // category1Id is what the server needs to get
      query.category1Id = category1id
      // Click the secondary menu
    }else if(category2id){
      query.category2Id = category2id
      // Click on the three-level menu
    }else {
      query.category3Id = category3id
    }
    // The params parameter is added while carrying the query parameter. The problem is that if there is no prams parameter, it is an empty object, and the judgment is also true. It feels that this judgment is meaningless
    if(this.$route.params){
      location.params = this.$route.params
    }
    this.$router.push(location)
  }
}
// The optimized callback method used in the Header component to jump to the Search component. The keyword defined here in data is not defined arbitrarily, but the parameters required by the background server when querying data
toSearch() {
  const location = {name:'search',params:{keyword:this.keyword || undefined}}
  if(this.$route.query){
    location.query = this.$route.query
  }
  this.$router.push(location)
},

Display of TypeNav global component in Search component

  1. The sort part of the TypeNav component (that is, the part excluding all product categories) is displayed in the Home part and hidden in the Search part
  2. At the same time, if you are not in Home, you need to show the sort (three-level navigation) when you move the mouse into the commodity classification and hide it when you leave
  3. The display and hiding need to be controlled by v-show. Since the dynamic control needs to be judged by the data in data: isShow:true
// Controls the display and hiding of srot
<div class="sort" v-show="isShow"></div>
// Add the move in and move out event above. The previous move out event is for the mouse to leave, the background color is hidden, and the function is not pulled out. Now the function is pulled out
<div @mouseleave="outItem" @mouseenter="isShow=true"></div>
// In addition to sending requests, you also need to make a judgment during mounting. If it is not a home component, it will be hidden
mounted() {
  this.$store.dispatch("getCategoryList");
  if(this.$route.path !== '/home'){
    this.isShow = false
  }
},
// If the callback left by the mouse is not home, make isShow false and leave hidden
outItem(){
  // Leave item and cancel adding class
  this.currentIndex = -1
  if(this.$route.path !== '/home'){
    this.isShow = false
  }
},
// 

Use vue animation to add transition effects

  1. Add transition effect to the content that needs transition, that is, the part that uses v-show, that is, sort. Therefore, you need to nest a < transition name = "sort" > tag outside sort, and define the name attribute, which is used as the prefix of the defined class
  2. You need to define three class es when you only consider entering but not leaving
    • Sort enter: indicates the start of entering the animation
    • Sort enter to: indicates the end of the animation
    • Sort enter active: indicates the duration of the whole animation process
// Wrap the part of the animation you want to assign
<transition name="sort"></transition>
// Redefined style in sort
&.sort-enter{
  height: 0;
  opacity: 0;
}
&.sort-enter-to{
  height: 461px;
  opacity: 1;
}
&.sort-enter-active{
  transition: all 0.2s;
}

There are too many three-level classification data in the optimization request

When TypeNav is mounted, it will send a request to vuex to obtain the data of three-level classification. When it is frequently switched from Home to Search, it will be destroyed and mounted continuously, and the same data requested will be re sent all the time
Question: why is TypeNav destroyed when switching when it is not a routing component? (I passed the beforeDestroy test and was indeed destroyed)
Thinking: I think it's because although TypeNav is not a routing component, it is used by the Home and Search routing components as a global component. When switching, it is actually switching between the Home component and Search component. If both components are destroyed, the TypeNav component contained in them must be destroyed
Solution: put the operation of dispatch request in main In the mounted of the JS entry file, the entry file is executed only once, the Vue instance is created only once, and there is only one Vue instance object. The acquisition request data is stored in vuex, which does not affect the acquisition and display of data in TypeNav

new Vue({
  render: h => h(App),
  router:router, // key and value are the same and can be omitted, but not omitted
  store:store,
  mounted(){
    // Optimize and put the dispatch request task in main JS
    this.$store.dispatch("getCategoryList");
  }
}).$mount('#app')

To dynamically display ListContainer and Floor components

mock

Generate random data and intercept ajax requests
Use steps:

  1. Install mock:npm i mockjs
  2. Create a mock file in the src directory
  3. Create a floor using the simulation data provided JSON file and banner JSON file (simulate the data required by banner and floor respectively)
  4. Create mockserver JS file, which is the file used by mock to simulate data
  5. In main Introduction of mockServer.js JS file, mock simulation data will take effect: import '@/mock/mockServer'

floor.json file:

[
  {
    "id": "001",
    "name": "Household Electric Appliances",
    "keywords": [
      "Energy saving subsidy",
      "4K television",
      "air cleaner",
      "IH rice cooker",
      "Drum washing machine",
      "Electric water heater"
    ],
    "imgUrl": "/images/floor-1-1.png",
    "navList": [
      {
        "url": "#",
        "text": "Hot"
      },
      {
        "url": "#",
        "text": "Large household appliances"
      },
      {
        "url": "#",
        "text": "Household appliances"
      },
      {
        "url": "#",
        "text": "Kitchen appliances"
      },
      {
        "url": "#",
        "text": "Seasonal electrical appliances"
      },
      {
        "url": "#",
        "text": "air/water purification"
      },
      {
        "url": "#",
        "text": "High end electrical appliances"
      }
    ],
    "carouselList": [
      {
        "id": "0011",
        "imgUrl": "/images/floor-1-b01.png"
      },
      {
        "id": "0012",
        "imgUrl": "/images/floor-1-b02.png"
      },
      {
        "id": "0013",
        "imgUrl": "/images/floor-1-b03.png"
      }
    ],
    "recommendList": [
      "/images/floor-1-2.png",
      "/images/floor-1-3.png",
      "/images/floor-1-5.png",
      "/images/floor-1-6.png"
    ],
    "bigImg": "/images/floor-1-4.png"
  },
  {
    "id": "002",
    "name": "Mobile communication",
    "keywords": [
      "Energy saving subsidy 2",
      "4K TV 2",
      "Air purifier 2",
      "IH Rice cooker 2",
      "Drum washing machine 2",
      "Electric water heater 2"
    ],
    "imgUrl": "/images/floor-1-1.png",
    "navList": [
      {
        "url": "#",
        "text": "Hot 2"
      },
      {
        "url": "#",
        "text": "Power 2"
      },
      {
        "url": "#",
        "text": "Household appliances 2"
      },
      {
        "url": "#",
        "text": "Kitchen appliances 2"
      },
      {
        "url": "#",
        "text": "Seasonal electrical appliances 2"
      },
      {
        "url": "#",
        "text": "air/Water purification 2"
      },
      {
        "url": "#",
        "text": "High end electrical appliances 2"
      }
    ],
    "carouselList": [
      {
        "id": "0011",
        "imgUrl": "/images/floor-1-b01.png"
      },
      {
        "id": "0012",
        "imgUrl": "/images/floor-1-b02.png"
      },
      {
        "id": "0013",
        "imgUrl": "/images/floor-1-b03.png"
      }
    ],
    "recommendList": [
      "/images/floor-1-2.png",
      "/images/floor-1-3.png",
      "/images/floor-1-5.png",
      "/images/floor-1-6.png"
    ],
    "bigImg": "/images/floor-1-4.png"
  }
]

banner.json file:

[
  {
    "id": "1",
    "imgUrl": "/images/banner1.jpg"
  },
  {
    "id": "2",
    "imgUrl": "/images/banner2.jpg"
  },
  {
    "id": "3",
    "imgUrl": "/images/banner3.jpg"
  },
  {
    "id": "4",
    "imgUrl": "/images/banner4.jpg"
  }
]

mockServer.js file:

// Introducing mock
import Mock from "mockjs"
// Import data
import banner from './banner.json'
import floor from './floor.json'
// Create the request path. The second parameter is data. We use the form of object to simulate the return format of data
Mock.mock('/mock/banner',{code:200,data:banner})
Mock.mock('/mock/floor',{code:200,data:floor})

Send ajax request to get mock simulated data

  1. Re encapsulate an axios object. The previous axios is used to send requests to the path beginning with api. This is specifically to send requests to the path beginning with mock. You only need to copy a copy of the previous aixos secondary encapsulation file and modify the baseURL
  2. Index. Under api The newly encapsulated axios object is introduced into the JS file
  3. Write the request interface function and send the request to the url address simulated by mock
  4. Because both banner data and floor data are data in the Home component, the data in the store is Home Write vuex dispatch request in JS to obtain data

Note: my view is directly in this file. I execute the function to send the request directly, but I can't view it on the console. Therefore, it should be noted that mock will intercept ajax requests, that is, the request will not be actually sent, so I won't view the sent request in the browser's network

Request the interface function file to add content. Be careful not to forget the newly encapsulated axios object:

// Get the interface function of simulating banner data through mock
export const reqBannerList = ()=>{
  return mockRequest({
    url:'/banner',
    method:'get'
  })
}
// Get the interface function that simulates floor data through mock
export const reqFloorList = ()=>{
  return mockRequest({
    url:'/floor',
    method:'get'
  })
}

store under home JS file complete content:

  1. Because you need to use a new request interface function, don't forget to introduce it
  2. The received data type defined in state depends on the data type returned. If the returned data is an array, it will be received with an array, and if the returned data is an object, it will be received with an object
// Import interface request function
import { reqCategoryList,reqBannerList,reqFloorList } from "@/api"
const state = {
  // The data type depends on the data type returned by the request
  categoryList : [],
  bannerList:[],
  floorList:[]
}
const mutations = {
  // Two parameters. The first parameter is state and the second parameter is the data to be stored
  RECEIVE_CATEGORYLIST(state,categoryList){
    state.categoryList = categoryList
  },
  RECEIVE_BANNERLIST(state,bannerList){
    state.bannerList = bannerList
  },
  RECEIVE_FLOORLIST(state,floorList){
    state.floorList = floorList
  }
}
const actions = {
  async getCategoryList({commit}){ // The parameter here is the context object, which contains some methods of $store. We only need to commit, so the structure is assigned
    // Call the interface request function to send the request, and return a promise instance. Use awati to receive the successful value of the promise instance, that is, the returned result
    // Synchronous code realizes asynchronous effect and good readability
    const result = await reqCategoryList() // Here, you have to wait for the result to return before you go back to execute the following code
    if(result.code === 200){
      commit('RECEIVE_CATEGORYLIST',result.data) // Although axios is processed and data is returned, the data in data is the data returned by the server
    }
  },
  async getBannerList({commit}){
    const result = await reqBannerList()
    if(result.code === 200){
      commit('RECEIVE_BANNERLIST',result.data)
    }
  },
  async getFloorList({commit}){
    const result = await reqBannerList()
    if(result.code === 200){
      commit('RECEIVE_FLOORLIST',result.data)
    }
  }
}
const getters = {}
// What is directly exposed is an object
export default {
  state, mutations, actions, getters
}

File mounting in listcontainer (the data of the rotation is used here) is a test of sending a request. You can check whether the data is obtained through vuex:

mounted(){
  this.$store.dispatch('getBannerList')
  this.$store.dispatch('getFloorList')
}

Get banner data and show it

  1. After the request is loaded in the container, the data is stored in the list vuex
  2. Get the data in vuex using the calculation attribute
  3. Use v-for to traverse the picture list and display the obtained picture data
    • The data of the picture is: ` imgUrl:"/images/banner1.jpg"
    • /Indicates that the root directory is the project directory, so we need to create the images directory under the public directory and put the pictures in this directory
// 1. Send request to get data
import {mapState} from 'vuex'
export default {
  name: "ListContainer",
  mounted(){
    this.$store.dispatch('getBannerList')
  },
  computed:{
    // You can check whether the calculated attribute is obtained in the component
    ...mapState({bannerList:state=>state.home.bannerList})
  }
};
// 2. Traversal display
<div class="swiper-slide" v-for="banner in bannerList" :key="banner.id">
  <img :src="banner.imgUrl" />
</div>

Get the data of the floor and display it

Special places:

  1. Floor data is used by floor components, but the number of floor components is dynamically generated according to the data
  2. Assuming that the data is obtained in the floor and two copies are obtained, it cannot be displayed. Therefore, the data of the floor cannot be obtained in the floor component, but in the Home component using the Footer component
  3. Then, dynamically generate the number of Floor components to be displayed according to the data v-for, and transfer the data to the Floor component through the parent component to the child component

Think: props declares the received attribute in the component, and the received attribute will be displayed in the_ Props object, but you can use this The floor directly obtains data, because there is a data agent, just like the data in data

// 1. Data obtained from dispatch request in home component
export default {
  name: "Home",
  // Registration and use of non global components
  components: { Floor, ListContainer },
  mounted(){
    this.$store.dispatch('getFloorList')
  },
  computed:{
    ...mapState({floorList:state=>state.home.floorList})
  }
};
// 2. Loop traversal generates multiple Floor components
<Floor v-for="floor in floorList" :key="floor.id" :floor="floor"></Floor>
// 3. Use pros to receive data in the Floor component
export default {
  name: 'Floor',
  // Declare the receiving attribute, through which you can get the attribute value, and the attribute will appear in the component_ In proprs, you can use this The floor is obtained directly, and there should be a data agent like the data in data
  props:['floor']
};
// 4. Just display the data in the Floor component. Pay attention to the obtained image path. You need to store the image in the public images directory

Use of Swiper

Specific use reference Official documents

  1. Installing swiper:npm i swiper@5
  2. Import js file: import Swiper from 'swiper'
  3. Introduce CSS file, because the rotation may be used by many pages, so it can be in Mian JS entry file: import 'wiper / CSS / wiper css'
    • If css is introduced in main and also in the current file, when packaging, webpack will detect the same content and will not package twice
  4. Deconstruction required for writing swiper (there are cases on the official website)
  5. Instantiate the wiper instantiation object and fill in the structure container to be managed

Note: the swiper cannot take effect until the page data structure is displayed, which is why direct use has no effect

Implement the rotation chart in ListContainer

  1. Why is new swiss() not effective in mounted directly? Because data is requested first, the structure is generated by traversing the data obtained by sending the request, that is, the structure is not ready yet
  2. Why is the rotation in the Floor effective after the timer is used: because it is a single page reference, the class name is the same, and the rotation container in the Floor is also selected by using class in new Swiper()
    • The first parameter of new Swiper() requires a container
    • Using ref can solve this problem

Use timer to solve:

mounted(){
  this.$store.dispatch('getBannerList')
  // Directly instantiating the wiper here will not take effect, because the dispatch request is an asynchronous task, which needs to obtain data and then traverse the generated structure. When the main thread continues to execute new Swiper, the structure data may not come back and the structure may not be generated (it needs to be generated according to the data traversal)
  setTimeout(() => {
    var mySwiper = new Swiper (this.$refs.bannerSwiper, {
      loop: true, // Loop mode options    
      // If pager is required
      pagination: {
        el: '.swiper-pagination',
        clickable:true // Click the dot of the pager to switch the rotation chart
      },  
      // If necessary, press the forward and backward buttons
      navigation: {
        nextEl: '.swiper-button-next',
        prevEl: '.swiper-button-prev',
      },
    }) 
  }, 2000);
},

Use watch and this$ Nexttick():

  1. Explain on the official website: the delayed callback is executed after the end of the next DOM update cycle. Use this method immediately after modifying the data to get the updated dom.
    • Understanding: the loop is v-for, which means that it will be executed once when there is no data. After the global traversal is completed and a new dom structure is generated, it will be executed once (the next dom update loop ends)
    • Understanding: after modifying the data, the dom parses again, and the latest dom can be obtained inside the callback of this method
  2. What the teacher said: nextTick will be executed only after the latest update of the page (note keyword: recent)
    • The difference between update() and update(): update is that the page will be executed as long as there is data update. The execution does not care whether it is the latest one or not
    • Understanding: for example, the v-for loop will update a lot of data. The callback will not be executed until all updates are completed, that is, the last time, that is, the last time (there must be a structure after the loop is completed)
watch:{
  // Monitor the changes of bannerList data from 0 to 4, and execute once there is a change
  bannerList:{
    handler(){
      // But it still can't take effect. Although the data is available, the structure is generated through v-for, that is, the structure doesn't completely appear
      this.$nextTick(function(){
        var mySwiper = new Swiper (this.$refs.bannerSwiper, {
        loop: true, // Loop mode options    
        // If pager is required
        pagination: {
          el: '.swiper-pagination',
          clickable:true // Click the dot of the pager to switch the rotation chart
        },  
        // If necessary, press the forward and backward buttons
        navigation: {
          nextEl: '.swiper-button-next',
          prevEl: '.swiper-button-prev',
        },
        }) 
      })
    }
  }
}

Implement the rotation chart in the Floor

Why can the rotation chart in the Floor component take effect directly in the mounted new Swiper():

  1. Because the data is not obtained from the Floor component, the data already exists and is passed through the parent component Home
  2. Therefore, when the Floor component is instantiated, the data must exist, and the structure does not need to wait for the data to be formed

To sum up:

  1. You need to send asynchronous requests to get data, so there is no data after the mount is completed, and the structure is incomplete
  2. After using the monitoring attribute, there is data, but the page needs to reuse v-for to generate a new structure, so the structure is not complete and needs to be combined with nextTick()
  3. In the case of data, the complete structure will be directly available after mounting
mounted() {
  var mySwiper = new Swiper(this.$refs.floorSwiper, {
    loop: true, // Loop mode options
    // If pager is required
    pagination: {
      el: ".swiper-pagination",
      clickable: true, // Click the dot of the pager to switch the rotation chart
    },
    // If necessary, press the forward and backward buttons
    navigation: {
      nextEl: ".swiper-button-next",
      prevEl: ".swiper-button-prev",
    },
  });
},

Encapsulated carousel assembly

Packaging preparation

Question: why does encapsulation use troublesome encapsulation
reflection:

  1. Because the most troublesome situation should be considered, such as the use of the rotation component in the Floor, and the data of the Floor is transmitted through Home, the Floor can directly transfer the existing data to the rotation component
  2. However, in the ListContainer component, you need to send a request to get the data in the ListContainer, and then pass the data to the rotation component
  3. Therefore, although monitoring is not required in the Floor component, it should be considered when packaging

Rewrite the rotation in ListContainer and Floor into the same structure. It is observed that:

  1. this.$ refs. The spinner container taken over by floorshipper is different, but don't worry, the structure will encapsulate everything (component-based development)
  2. The monitored content is different. The monitored content needs to be transmitted through the parent component used. In the rotation component, only one props needs to be specified to receive it

In Floor:

watch: {  
  floor: {
    // The floor component monitors the passed floor data, but the data will not change, so it will be executed once
    immediate:true,
    handler() {
      this.$nextTick(function () {
        var mySwiper = new Swiper(this.$refs.floorSwiper, {
          loop: true, // Loop mode options
          // If pager is required
          pagination: {
            el: ".swiper-pagination",
            clickable: true, // Click the dot of the pager to switch the rotation chart
          },
          // If necessary, press the forward and backward buttons
          navigation: {
            nextEl: ".swiper-button-next",
            prevEl: ".swiper-button-prev",
          },
        });
      });
    },
  },
},

In ListContainer:

watch:{
  // Monitor the changes of bannerList data from 0 to 4, and execute once there is a change
  bannerList:{
    immediate:true, // It's useless to cater
    handler(){
      // But it still can't take effect. Although the data is available, the structure is generated through v-for, that is to say, the structure doesn't appear completely
      this.$nextTick(function(){
        var mySwiper = new Swiper (this.$refs.bannerSwiper, {
        loop: true, // Loop mode options    
        // If pager is required
        pagination: {
          el: '.swiper-pagination',
          clickable:true // Click the dot of the pager to switch the rotation chart
        },  
        // If necessary, press the forward and backward buttons
        navigation: {
          nextEl: '.swiper-button-next',
          prevEl: '.swiper-button-prev',
        },
        }) 
      })
    }
  }
}

Encapsulate Carousel and use

Package code note:

  1. Swiper needs to be introduced
  2. Styles do not need to be imported, but Swiper styles need to be imported globally when used
  3. Receive attribute props:['bannerList '] so the parent component needs to use bannerList as the attribute name when passing data
  4. Because of sharing, it is defined as a global component. Don't forget to register globally
<template>
  <div class="swiper-container" ref="bannerSwiper">
    <div class="swiper-wrapper">
      <div class="swiper-slide" v-for="banner in bannerList" :key="banner.id">
        <img :src="banner.imgUrl" />
      </div>
    </div>
    <!-- If pager is required -->
    <div class="swiper-pagination"></div>
    <!-- If you need navigation buttons -->
    <div class="swiper-button-prev"></div>
    <div class="swiper-button-next"></div>
  </div>
</template>

<script>
// Introducing js
import Swiper from 'swiper'
export default {
  name: "Carousel",
  // The parent component needs to pass the attribute with this name
  props:['bannerList'],
  watch: {
    // Monitor the changes of bannerList data from 0 to 4, and execute once there is a change
    bannerList: {
      immediate: true, // It's useless to cater
      handler() {
        // But it still can't take effect. Although the data is available, the structure is generated through v-for, that is to say, the structure doesn't appear completely
        this.$nextTick(function () {
          var mySwiper = new Swiper(this.$refs.bannerSwiper, {
            loop: true, // Loop mode options
            // If pager is required
            pagination: {
              el: ".swiper-pagination",
              clickable: true, // Click the dot of the pager to switch the rotation chart
            },
            // If necessary, press the forward and backward buttons
            navigation: {
              nextEl: ".swiper-button-next",
              prevEl: ".swiper-button-prev",
            },
          });
        });
      },
    },
  },
};
</script>

Usage Note:

  1. Because components are used, there is no need to introduce swiper js
  2. When transferring data, pay attention to the attribute name required by the component
// listContainer is used in use. It passes the fresh data obtained by sending a request
<Carousel :bannerList="bannerList"></Carousel>
// It is used in the Floor and transmits the rotation content part in the Floor (the whole Floor information transmitted by the parent component Home of the Floor)
<Carousel :bannerList="floor.carouselList"></Carousel>

Topics: Front-end Vue Vue.js PostMan