Project running

# cloning to local git clone [email protected]: Hanxueqing/Douban – Movie. Git

Install by NPM install

# Enable local server localhost:8080 YARN serve

Publish environment YARN Build

Project development

1. Install vuE-CLI3 scaffolding

Now the use of front-end engineering development projects is the mainstream trend, that is to say, we need to use some tools to build vUE development environment, in general we use Webpack to build, here we directly use vUE official provided, webpack-based scaffolding tool: VUE-CLI.

(1) Install webpack globally

cnpm install webpack -g

(2) Install YARN globally

cnpm install yarn  -g
Copy the code

(3) Global installation vUE – CLI

cnpm install -g @vue/cli

OR

yarn global add @vue/cliCopy the code

(4) View the installation result

My computer has been installed, in order to execute the command:

Node -v yarn -v vue -v (note that there is a capital "V")Copy the code

Note: Node.js version 8 or 8+ is required

If the corresponding version number is displayed, the installation is successful



2. Build the project with VUe-CLI

(1) Create a project

Vue Create Douban (project name)Copy the code

(Note that names cannot have uppercase letters, otherwise an error will be reported

Sorry, name can no longer contain capital letters)

The first vue-Model is the Settings I saved, not when I first created it. Default is the default setting that will install the Babel and ESLint modules for you. We chose the third Manually select features configuration ourselves.



Enter space to select the current option and a to select all.

(Bold is the configuration item to select)

Babel installs this module to recognize ES6 syntax, but otherwise only ES5 syntax

TypeScript is a superset of JavaScript that supports the ECMAScript 6 standard.

Progressive Web App (PWA) Support is a specialized subject that can be used for offline storage. It can also be accessed when the phone is disconnected from the Internet. We do not need this page at present, so we will not install it for now.

The Router routing

Vuex global status management

CSS pre-processors CSS pretreatment language

Linter/Formatter helps us write better code

Unit Testing

E2E Testing

After selecting these four items, type Enter

The next configurations are as follows:

  1. To determine whether to configure the route in history mode, enter n and press Enter

  2. Since we have just chosen the CSS preprocessor language, we will choose the stable version of Sass/SCSS (with Node-sass)

  3. Where do you need to store your configuration files, let’s say in package.json

  4. And finally, do you want to save these Settings as a preset file to be used in the future, so you don’t have to configure them manually, you can use them in the future, and I’ve saved them before, so I’m going to go ahead and select n.



Press Enter to download, depending on your current Internet connection speed.



After the installation is successful, the system prompts you to enter the Douban folder and run yarn Serve to start the listening.



(2) Load the compilation file

Copy the vue.config. js compilation file to the douban folder

3. Clean up the SRC folder

(1) the router

Create a router folder, drag the router.js file into it, rename it to index.js, and delete the contents of routes.



(2) views

Delete the two. Vue files in the Views folder, the logo image in assets folder and the HelloWorld component in components folder.

(3) App. Vue

The app. vue file style was deleted, and the div content with the ID App was deleted.



(4) Create a new page in the views

Create homepage, Book, Audio, broadcast, Group, my page in sequence under the views folder. Create index.vue file under the folder. The file with vue suffix contains template, script, style. Webpack is loaded with vue-loader when it parses. Vue files.

(5) Configure routes on the Router

In the router folder, create js route files of home page, book video, broadcast, Group, and my page in turn. Configure a name for each route, so that we can find this route component through named route in the future, and finally import it in index.js.

(6) Introduced in app.vue

(7) Write style files

Create a new stylesheets folder

Webpack does not process underlined files during packaging to avoid repeated packaging

Create the _base.scSS base style in sequence

_commons.scss common style

_mixins.scss mix style

_reset. SCSS resets the style

Finally, import them in the main. SCSS folder

@import "_base.scss";
@import "_mixins.scss";
@import "_reset.scss";
@import "_commons.scss";Copy the code

Introduce styles as a module in main.js

// import the main. SCSS file import"./stylesheets/main.scss"Copy the code

4. Tabbar components

(1) Slot slot: named slot, anonymous slot

First, we will introduce the normal status icon and selected status icon, write a style, the normal status icon named normalImg, selected status icon named activeImg, here we use the slot slot. You can pass the content to be inserted into the parent component by writing the slot label in the child component and assigning the name value as the named slot.

Child components:

<span><slot name = "normalImg"></slot></span>
<span><slot name = "activeImg"></slot></span>Copy the code

The parent component:

<TabItem>
            <img slot = "normalImg" src = ".. /.. /assets/ic_tab_home_normal.png" alt = "">
            <img slot = "activeImg" src = ".. /.. /assets/ic_tab_home_active.png" alt = "">
</TabItem>Copy the code

(2) V-if/V-else instruction: control show and hide

The first step is to switch the icon from normal style to selected style. We need to set a flag attribute to control the display of normalImg and activeImg. Here we use v-if and V-else instructions

<span v-if = ! "" flag"><slot name = "normalImg"></slot></span>
<span v-else><slot name = "activeImg"></slot></spanCopy the code

(3) There are two ways for the parent component to transmit value to the child component: event binding and custom event

The parent passes unique TXT, mark, sel attributes and changeSelected methods to the child. The parent passes selected values to sel via property bindings:

The parent component:

<TabItem txt = "I" mark = "mine" :sel = "selected" :changeSelected = "changeSelected">Copy the code

Child components receive in turn:

props:["txt"."mark"."sel"."changeSelected"]Copy the code

By binding the “change” event in the child component, let the parent component change the selected value and then pass it to the child component. By matching the mark value to determine the return value, so as to control the current icon state.

The child component binds the click event to trigger the change method:

<div class = "tabitem" @click = "change">Copy the code

Subcomponent change method:

changeThis.changeselected (this.mark); }Copy the code

Parent component changes SEL value:

changeSelected(val){
            this.selected = val;
        }Copy the code

Father components can also use a custom event to child components transfer method, receiving child components don’t have to through the props, but directly in the way through this. $emit to use, the first parameter is the binding in the parent’s own event name, the second parameter is passed to the parameters of the parent, triggered by this method in the subcomponents parent component method.

The parent component:

<TabItem txt = "Team" mark = "group" :sel = "selected" @changeSelected = "changeSelected">Copy the code

Child components:

change(){// Custom event communication method this.$emit("changeSelected",this.mark)
        }Copy the code

(4) Computed attributes

The child component evaluates and returns the falg value, which changes due to dependence on external values, so we write flag in computed property:

computed:{
        flag() {if(this.mark === this.sel){
                return true
            }
            return false}}Copy the code

(5) Programmatic navigation

In the sub-component, the programmatic navigation can match different routes according to different MARK attributes, and then click the icon to jump to the corresponding page

change(){// Custom event communication method this.$emit("changeSelected",this.mark) // Programmatically navigate this.$router.push("/" + this.mark)
        }Copy the code

Because we wrote Tabbar in app.vue before, it was created only once and appeared in all pages, but we did not need Tabbar in some pages, so we could not write it in the global, and we introduced it in the page where we needed it. In this case, we found that although we could click to jump to the page, The bottom icon, however, does not change color because the Tabbar goes from creation to destruction every time it jumps, so it can no longer be assigned a fixed value in data.

The original code:

data() {return{
            selected:"home"}}Copy the code

When we print this, we can get its name value in $route. We assign the name value to data and pass it directly to the child component for judgment, no need to pass method.



This.$route.name determines which element in the parent component is selected.

The parent component:

data() {return{
            selected:this.$route.name,
        }
    }Copy the code

It would be tedious and cumbersome to write all the subcomponent data in the template, so we can write the data in the data object and load it on the page using the V-for loop:

In data, create an array of footers and place the object data in the array:

footers:[
                {id:1,txt:"Home page",mark:"home",normalImg:".. /.. /assets/ic_tab_home_normal.png",activeImg:".. /.. /assets/ic_tab_home_active.png"},
                {id:2,txt:"Book and Video",mark:"audio",normalImg:".. /.. /assets/ic_tab_audio_normal.png",activeImg:".. /.. /assets/ic_tab_audio_active.png"},
                {id:3,txt:"Radio",mark:"broadcast",normalImg:".. /.. /assets/ic_tab_broadcast_normal.png",activeImg:".. /.. /assets/ic_tab_broadcast_active.png"},
                {id:4,txt:"Team",mark:"group",normalImg:".. /.. /assets/ic_tab_group_normal.png",activeImg:".. /.. /assets/ic_tab_group_active.png"},
                {id:5,txt:"I",mark:"mine",normalImg:".. /.. /assets/ic_tab_mine_normal.png",activeImg:".. /.. /assets/ic_tab_mine_active.png"}]Copy the code

(6) V-for instruction

Loop through footers in the TabItem tag:

<TabItem
            v-for="foot in footers"
            :key = "foot.id"
            :txt = "foot.txt"
            :sel = "selected"
            :mark = "foot.mark"
        >
            <img slot = "normalImg" :src = "foot.normalImg" alt = "">
            <img slot = "activeImg" :src = "foot.activeImg" alt = "">
        </TabItem>Copy the code

However, if you refresh the page at this point, you will find that the ICONS do not load because webPack does not recognize our relative path during the packaging process, and it will output the contents in quotes as they are



We need to use the require load path to introduce the image as a module

{id:1,txt:"Home page",mark:"home",normalImg:require(".. /.. /assets/ic_tab_home_normal.png"),activeImg:require(".. /.. /assets/ic_tab_home_active.png")}Copy the code

And WebPack will convert the image we introduced into base64-bit format, which is equivalent to putting a piece of text in a web page so you don’t have to send a remote request when you visit it again.



After conversion, its volume will become larger, but not much larger than before. The network request Time will be reduced, and the Time will become 0ms, which speeds up access efficiency and improves user experience. This is mainly for small images, up to 20KB.



5. 404 page

Redirect: Indicates route redirection

When we access a path that doesn’t exist, we redirect the page to NotFound, which means that users who enter a path that doesn’t exist in the routing table are invariably redirected to a 404 page.

Create a Notfound folder in views, write a 404 page in index.vue, and configure a route in router.

In index.js, write:

 routes: [
    {path:"/",redirect:"/home"},
    home,audio,broadcast,group,mine,
    {path:"/notfound",component:()=>import("@/views/Notfound")},
    {path:"*",redirect:"/notfound"},]Copy the code

{path:”*”,redirect:”/ notFound “} should be at the end of the redirect. Redirect :”/ notFound “} should be at the end of the redirect.

(2) Router-link: indicates the route label

Added the ability to click back to the home page

<router-link to = "/"</router-link>Copy the code

6. Write the header

(1) REM: responsive layout

Here we use REM to calculate the size and achieve a responsive layout on the mobile side.

First we’ll create a new modules folder and write rem.js with iphone6 as the main one

Document. The documentElement. Style. FontSize = document. DocumentElement. ClientWidth / 3.75 +"px"
window.onresize = function() {document. The documentElement. Style. FontSize = document. The documentElement. ClientWidth + / 3.75"px"
}Copy the code

Introduce the rem.js file in main.js

// Import the rem.js file"./modules/rem.js"Copy the code

(2) font-awsome: font icon library

Download the CSS file package from the font-awsome website, place it in the public folder, and introduce styles in index.html.



Use the font – awsome:

<i :class = "['fa','fa-' + home]"></i>Copy the code

Now we want the header to change dynamically when the route changes. Let’s change the content to this form:

In the template:

<div class = "left">
            <i :class = "['fa','fa-' + icon]"></i>
            <span>{{title}}</span>
        </div>Copy the code

In the data:

data() {return{
            icon:"home",
            title:Douban Home Page,}},Copy the code

In this case, we need to use the route guard. When switching routes, we can do some business logic. First, we need to introduce global routes in the page:

import router from  "@/router"Copy the code

Write the global front-guard router. BeforeEach, use the switch statement to match:

created(){
        router.beforeEach((to,from,next)=>{
            switch(to.name){
                case "home":
                    this.title = Douban Home Page
                    this.icon = "home"
                    break;
                case "audio":
                    this.title = Douban Movie and Music
                    this.icon = "audio-description"
                    break;
                case "broadcast":
                    this.title = Douban Broadcast
                    this.icon = "caret-square-o-left"
                    break;
                case "group":
                    this.title = Douban Group
                    this.icon = "group"
                    break;
                case "mine":
                    this.title = "Douban mine"
                    this.icon = "cog"
                    break;
                default:
                    break; } next(); })}Copy the code

Since we wrote the Header component in the global App, it will be displayed on every page, but we want it to be displayed only when the specified route is matched. In this case, we need to set an isShow parameter value to control its display and hiding.

In the data:

data() {return{
            icon:"home",
            title:Douban Home Page,
            isShow:true}}Copy the code

In switch: isShow is true in all cases matched, default is false in all cases matched

switch(to.name){
                case "home":
                    this.title = Douban Home Page
                    this.icon = "home"
                    this.isShow = true
                    break;
                case "audio":
                    this.title = Douban Movie and Music
                    this.icon = "audio-description"
                    this.isShow = true
                    break;
                case "broadcast":
                    this.title = Douban Broadcast
                    this.icon = "caret-square-o-left"
                    this.isShow = true
                    break;
                case "group":
                    this.title = Douban Group
                    this.icon = "group"
                    this.isShow = true
                    break;
                case "mine":
                    this.title = "Douban mine"
                    this.icon = "cog"
                    this.isShow = true
                    break;
                default:
                    this.isShow = false
                    break;

            }Copy the code

Then add v-if to div and assign the value isShow

<div class = "app-header" v-if = "isShow">Copy the code

So there are two ways to implement component show and hide:

(1) Reference it wherever it is needed, such as Tabbar.

(2) The switch works with the routing guard to make a judgment. V-if is used to display where it is needed, such as Header.

7. Banner rotation chart

(1) Swiper: Rote map component

Install and download two modules swiper and Axios:

CNPM I swiper-s or YARN add swiper-s CNPM I axios-sCopy the code

It is best not to mix and use CNPM as soon as you start using it

Installation success display



Introduce the swiper style in main.js

// Import swiper.min. CSS style file import'swiper/dist/css/swiper.min.css'Copy the code

Create a Banner folder in the Components component, write the index.vue file, and import swiper:

import Swiper from "swiper"Copy the code

Write swiper content and loop renderings of clearly passionate images to your page

<div class = "swiper-container">
        <div class = "swiper-wrapper">
            <div
                class = 'swiper-slide'
                v-for = "banner in banners"
                :key = "banner.id"
            >
                <img width = "100%" :src = "getImages(banner.images.small)" alt = "">
            </div>
        </div>
        <div class = "swiper-pagination"></div>Copy the code

Since we are using douban API, we will encounter the problem of picture 403. Please refer to this article for specific solutions:

Blog.csdn.net/jsyxiaoba/a…

The article gives us a way to solve this problem, and we will introduce it here

Methods :{getImages(_url){if( _url ! == undefined ){let _u = _url.substring( 7 );
                return 'https://images.weserv.nl/? url='+ _u; }}},Copy the code

Since we’ll be using this function again, we’ll export it in module:

export default (_url) => {
    if(_url ! == undefined) {let _u = _url.substring(7);
        return 'https://images.weserv.nl/? url=' + _u;
    }
    return true
}Copy the code

For later use, just introduce:

import getImages from "@/modules/getImg"Copy the code

Register the method in Methods:

 methods:{
        getImages
    }Copy the code

To use the function name, pass the image SRC as an argument:

<img width = "100%" :src = "getImages(movie.images.small)" alt = "">Copy the code

Let’s instantiate Swiper

new Swiper(".home-banner",{
                    loop:true,
                    pagination:{
                        el:".swiper-pagination"}})Copy the code

We try to make a clean integration of swiperslide, swiperslide, swiperslide, swiperslide, swiperslide, swiperslide, swiperslide, swiperslide, swiperslide, swiperslide, swiperslide. It internally generates a new virtual DOM to compare to the last virtual DOM structure, and then generates a new real DOM. This process takes time, but we instantiated it immediately, so the instantiation is long over by the time the real DOM is rendered.

The solution is to avoid this problem by having to wait until a new real DOM rendering is complete due to data changes.

(2) This.$nextTick function

So we need to write the instantiation process on this. $nextTick callback function, in the function of operation is to wait for the data updating of new virtual dom rendering of the page into a true dom really renders before execution, is simply wait until all page rendering is finished, then instantiated operation.

this.$nextTick(()=>{// In this function, because the data changes cause the page to generate a new real DOM, all rendering is complete new Swiper(".home-banner",{
                    loop:true,
                    pagination:{
                        el:".swiper-pagination"}})})Copy the code

Running effect of banner rotation diagram:



(3) AXIos: Send Ajax asynchronous request data

Axios is a Promise-based HTTP library that can be used in browsers and Node.js.

Its main functions:

  • Created from the browserXMLHttpRequests
  • From the node. Js to createhttprequest
  • Supporting Promise API
  • Intercept requests and responses
  • Transform request data and response data
  • Cancel the request
  • Automatically convert JSON data
  • The client supports defenseXSRF

Since there are so many places where data requests need to be made, it would be a bit of a pain to import them every time. You can bind axios directly to vue’s prototype property, which can then be accessed via this.$HTTP:

// import axios from"axios"
Vue.prototype.$http = axios;Copy the code

(4) Cross-domain solution: reverse proxy

Since we are now accessing the picture of the remote port locally, there will be cross-domain problems. Here, we solve the cross-domain problems through the way of reverse proxy, and configure the proxy in the vue.config.js configuration file.

Proxy: {// Reverse proxy is used to solve cross-domain problems"/api":{
                target:"http://47.96.0.211:9000"// changeOrigin:true// Will you change the domain name pathRewrite:{// what does it start with"^/api":""}}}, // Set the proxyCopy the code

When the final access is made, the/API will be cleared automatically and the following path will be concatenated to the destination domain name:

this.$http.get("/api/db/in_theaters", {Copy the code

As soon as the configuration file changes, we need to manually restart the listener!

(5) Loading pictures

When you first enter the page, if no data is loaded, an empty block will be displayed. You can add a loading diagram to indicate that data is being loaded, and hide data when it is loaded to improve user experience.

The loading image is placed under the assests folder. This loading diagram can also be used elsewhere, so we introduced it in the public style:

.loading{ background:url(.. /assets/index.svg) no-repeat center; background-size:10%; Height: 2.4 rem}Copy the code

As the initial value of the banner is set to NULL in data, the label can be displayed or hidden according to the value of the banner and the V-if/V-else command:

<div class = "loading" v-if = ! "" banners"></div>
        <div class = "swiper-wrapper" v-else>Copy the code

Introduce the Banner component on the home page

<Banner></Banner>Copy the code

Loading graph running effect:



8. Home page list

(1) Filter: indicates a filter

So what we’re going to do is we’re going to write a MovieBox component on the front page, and then we’re going to have a nested MovieItem component inside of it, and we’re going to request data from the MovieBox and pass it as a property to the MovieItem.

data() {return{
            movies:null
        }
    },
    created(){
        this.$http.get("/api/db/in_theaters",{
            params:{
                limit:6
            }
        }).then(res=>{
            this.movies = res.data.object_list
        })
    }Copy the code

MovieItem receives Movies objects:

props:{
        movie:Object
    }Copy the code

Insert data into a page:

<div class = "movieitem">
        <div class = "main_block">
            <div class = "img-box">
                <img width = "100%" :src = "getImages(movie.images.small)" alt = "">
            </div>
            <div class = "info">
                <div class = "info-left">
                    <div class = "title line-ellipsis">{{movie.title}}</div>
                    <div class = "detail">
                        <div class = "count line-height""> <span class =" max-width: 100%; clear: both; min-height: 1em"sc">{{movie.collect_count | filterData}}</span></div>
                        <div class = "actor line-height line-ellipsis">
                            <span class = "a_title"Word-wrap: break-word! Important; "> <span style =" max-width: 100%"a_star line-ellipsis">{{movie.directors[0].name}}</span>
                        </div>
                        <div class = "rating line-height"</div> </div> <div class = {{movie.rating. Average}}</div> </div> <div class ="info-right"> 
                    <div class = "btn"Purchase tickets > < span > < / span > < / div > < / div > < / div > < / div > < / div >Copy the code

Loop rendering to the page in the MovieBox:

<div class = "moviebox">
        <div class="loading" v-if=! "" movies"></div>
        <MovieItem
            v-else
            v-for = "movie in movies"
            :key = "movie.id"
            :movie = "movie"
        ></MovieItem>
    </div>Copy the code

When we request data from the back end, sometimes it does not meet our actual needs. For example, the playback quantity returns a large number, which is inconvenient to read and watch. At this time, we need to set a filter filter to process the data into the format we want. Here we set a filterData method. If we receive more than 10000 data, we divide the current number by 10000, leaving a decimal place by.tofixed (), and concatenating a “million” to display on the page. Filter must have a return value. After processing, we return data.

Filters :{filterData(data){// console.log(typeof data) //num Number typesif(data > 10000){
                data = data /10000;
                data = data.toFixed(1)
                data += "万"
            }
            returndata; }}Copy the code

In the component by “|” to use:

<div class = "count line-height""> <span class =" max-width: 100%; clear: both; min-height: 1em"sc">{{movie.collect_count | filterData}}</span></div>Copy the code

(2) Mint-UI: UI library

Install the mint – UI:

cnpm i mint-ui -SCopy the code

After successful installation:



The official website provides two ways to import mint-UI components. The first way is to import mint-UI components as a whole, which is relatively large; the second way is to import mint-UI components as required, which is recommended.





To use the second method, you also need to install a plug-in:

npm install babel-plugin-component -DCopy the code



Add the following to the babel.config.js file:

"plugins": [["component", 
    {
      "libraryName": "mint-ui"."style": true}]]Copy the code

If you encounter the following problems when installing the Babel module, please refer to this article to solve them:

Segmentfault.com/a/119000001…



(3) Lazyload: lazy loading of components

Implement lazy loading function

Start with the lazy loading module in Mint-UI

// Import mint-UI module import {Lazyload} from"mint-ui"
Vue.use(InfiniteScroll);Copy the code

Change the SRC attribute of the image to v-lazy

<img width = "100%" v-lazy = "getImages(movie.images.small)" alt = "">Copy the code

This allows the page to render images lazily, loading only when the user slides the image



For images that are not loaded, the real SRC attribute is temporarily stored in data-src



(4) InfiniteScroll: unlimited loading

A plug-in for pull-up loading is also available in mint-UI called InfiniteScorll, again referenced in main.js

// Import {Lazyload, InfiniteScroll} from from mint-UI"mint-ui"
Vue.use(Lazyload);
Vue.use(InfiniteScroll);Copy the code

To use infinite scroll, add the V-infinite-scroll directive to the outermost div tag in the MovieBox module.

<div class = "moviebox"
        v-infinite-scroll="loadMore"
        infinite-scroll-disabled="loading"
        infinite-scroll-distance="10"
    >Copy the code



V-infinite scroll: indicates the method that will be triggered when the scroll distance is smaller than the threshold. The default value is false

Infinite -scroll-disabled: If true, infinite scrolling will not be triggered

Infinite – Scroll distance: threshold of rolling distance (pixels) for trigger loading method

Infinite -scroll-immediate-check: if true, the command is bound to an element and checks whether the loading method needs to be performed immediately. This is useful in the initial state when the contents might not fill the container. , the default value is true, false will not execute the loadMore method

We wrote the loadMore method in Methods for infinite scrolling

methods:{
        loadMore(){
            console.log("loadmore")}}}Copy the code

As long as infinite scrolling is enabled, the loadMore method is executed once by default when the page is created, so we wrapped the code we wrote in Created into a getMovies method in methods and fired in loadMore

methods:{
        loadMore(){
            console.log("loadMore")
            this.getMovies();
        },
        getMovies(){
            this.$http.get("/api/db/in_theaters",{
            params:{
                limit:6
            }
            }).then(res=>{
                this.movies = res.data.object_list
            })
        }
    }Copy the code

Infinite scrolling is started only when the value of infinite-scroll-disabled is false. We assign loading to it, so we need to define loading in data and assign false to enable it to trigger infinite scrolling by default.

data() {return{
            movies:null,
            loading:false,// Default trigger infinite scroll}}Copy the code

As we slide down, we need to request the parameters of the second page, so we need to use the page attribute. When we request the data, we need to implement dynamic loading, so we need to configure the limit and page separately.

data() {return{
            movies:null,
            loading:false// Infinite scrolling is triggered by defaultlimit:6,
            page:1,
        }
    }Copy the code

Using destruct assignment to params, execute page++ in the.then function

getMovies(){//let{page,limit} = this;
            this.$http.get("/api/db/in_theaters",{
            params:{
                limit,
                page
            }
            }).then(res=>{
                this.movies = res.data.object_list
                this.page++
            })
        }Copy the code

At this point, we find that the requested data overwrites the previous data, and the effect we want to achieve is to accumulate on the basis of the first page, so we need to initialize movies into an empty array.

movies:[],Copy the code

Concat methods use arrays to concatenate arrays and return new arrays assigned to movies.

// Concatenate the array and return the new array this.movies = this.movies. Concat (res.data.object_list)Copy the code

At this point, we can slide the new content, but there is another problem. According to the data request results, the same loadMore method is fired multiple times



We only need to request once, so we need to temporarily turn off loadMore while we request data

this.loading = true;Copy the code

And then when the data comes back it’s opened in the.then function

this.loading = false;Copy the code

This will not trigger the infinite scroll method very often, but now it will send data requests even if there is no more data, so we need to set a parameter to monitor if there is more data, alert the user if there is no data, and prevent subsequent operations

hasMore:true// If there is more data, the default is more dataCopy the code

If so, hasMore is set to false and return false to prevent subsequent operations. This step should be placed before Page ++

if(this.limit * this.page >= res.data.total){// Determine if there is more data this.hasmovies =false// Assign hasMovies to when there is no more datafalseTo return tofalse, do not execute the following page++return false
                    }
this.page++Copy the code

Write a judgment statement in loadMore. If hasMore is false, turn off infinite scrolling and stop the getMovies method so that ajax requests are no longer sent and loadMore is not triggered as often

loadMore() {if(! this.hasMore){ this.loading =true// Turn off infinite scrolling when there is no more data and returnfalseDo not perform the following operationsreturn false
            }
            this.getMovies();
        }Copy the code



(5) Toast: pop-up box components

In order to give the user a good experience, you need to give the user a hint when the data is loaded. In this case, you need to use the Toast popup component and load the module in the MovieBox first, because

Toast

Cannot be used as global variables, only in real time

The introduction of

.

import {Toast} from "mint-ui"Copy the code

Duration is the number of milliseconds that will last, and if you want to keep it in display all you need to do is assign -1. Executing the Toast method returns an instance of Toast, each with a close method that is used to manually close the Toast.



Before requesting data, place a Toast to remind the user that the data is loading

let instance = Toast({
                message: 'Loading... ',
                duration: -1,
                iconClass:"fa fa-cog fa-spin"
            })Copy the code

Call the close method in the. Then function to close the popup after loading

instance.close();Copy the code

Since I failed to use the Toast style in Mint-UI, I used Vant instead. Vant is a lightweight and reliable library of mobile Vue components with parameters similar to mint-UI, defined by vent.toast:

const toast1 = vant.Toast.loading({
                message: 'Loading now',
                duration: 0,
            })Copy the code

Duration 0 is not automatically closed; assign it to a constant, which is closed by the.clear () method.

toast1.clear();Copy the code



Toast popover effect:



9. Switch tabs

(1) Watch monitors data changes

We need to add a hot and upcoming TAB to the home page, adopt the method of data dynamic loading, define a NAVS array in data

data() {return{
        type:"in_theaters",
        navs:[
          {id:1,title:"It's on fire.".type:"in_theaters"},
          {id:2,title:"Coming soon".type:"coming_soon"},],}}Copy the code

Loop through the array in span, render the data on the page, add the calss name for “active”, and the method for clicking on the event. When clicking on the option, change the type value, and class determines if type is equal to nav.type. Implements the effect that the selected option becomes selected.

<div class = "navbar""> nav in navs" :key = "nav.id" :class = "{'active':type === nav.type}" @click = "type = nav.type" >{{nav.title}} Copy the code

Since we requested data in MovieBox using the address/API /db/in_theaters, we can only request data that is currently being streamed, and we need to pass the type value to MovieBox

<MovieBox :type = "type"></MovieBox>Copy the code

MovieBox receive

props:["type"].Copy the code

Change intheaters to this.type to dynamically switch requests

this.$http.get("/api/db/" + this.type,{Copy the code

In order to display different data when clicking on a switch, we need to have the MovieBox component perform subsequent operations based on changes in type. In this case, we need to use Watch to monitor changes in type, do some business logic when type changes, and empty the original movies array. Page starts at the first page, hasMore is assigned to true, and the getMovies method is retriggered to request more data.

watch:{
        type(){
            this.movies = [];
            this.page = 1;
            this.hasMore = true; this.getMovies(); }}Copy the code

TAB switching effects:



10. Fix options bar

(1) Created hook function

We need to fix the options bar when the page scrolls down to a certain height. To do this, we first define a data isfixed in data with an initial value of false

isfixed:false.Copy the code

Set to true when scrolling to a certain height, while dynamically binding class to options bar and page content, adding fixed positioning styles

<div class = "tab" :class = "{fixedTop:isfixed}">
<div :class = "{fixedBox:isfixed}">Copy the code

In the created function, we listen for the Scroll event to get the page’s scroll height. In order to avoid this event being triggered repeatedly, this event is triggered when both the scroll height is greater than 50 and isfixed is false.

 created(){// Initialize some lifecycle related events window.adDeventListener ("scroll",e=>{// Get the roll heightlet sTop = document.documentElement.scrollTop || document.body.scrollTop;
        if(sTop >= 50 && ! this.isfixed){ this.isfixed =true;
        }else if(sTop < 50 && this.isfixed){
          this.isfixed = false; }})Copy the code

ListenScroll () = listenScroll(); listenScroll() = listenScroll(); listenScroll() = listenScroll(); listenScroll() = listenScroll();

Methods :{listenScroll(e){// Obtain the scroll heightlet sTop = document.documentElement.scrollTop || document.body.scrollTop;
        if(sTop >= 330 && ! this.isfixed){ this.isfixed =true;
        }else if(sTop < 300 && this.isfixed){
          this.isfixed = false; }}}Copy the code

Add this method to created:

created(){// Initialize some lifecycle related events window.adDeventListener ("scroll",this.listenScroll)
    },Copy the code

BeforeDestory hook function

Then destroy this method when leaving the current page:

beforeDestory() {/ / component replacement, equivalent to a component is destroyed, destroy operation on the window. The removeEventListener ("scroll",this.listenScroll)
    },Copy the code

This way it does not interfere with the business logic of other pages

11. Record caching

(1) keep-alive: cache tag

When switching between components, we want to keep the home page browsed and not render it repeatedly, so we need to use the keep-alive tag to wrap the content that needs to be cached, and use the include attribute to select the component whose name matches

<keep-alive include = "home">
      <router-view></router-view>
</keep-alive>Copy the code

At this point, our home component is cached, so his created function is executed only once, it is not destroyed, and the beforeDestory method is not executed, so our previous methods need to be written in the activated and deactivated lifecycle hook functions.

(2) Activated and Deactived life cycle functions

Activated and deactivated are triggered only in nested components within the tree.

activated(){
      window.addEventListener("scroll",this.listenScroll)
    },
    deactivated(){
      window.removeEventListener("scroll",this.listenScroll)
    }Copy the code

The problem is that when we leave the component, it will complete the data request once, so we need to set this. Loading to true in the deactived method, turn off infinite scrolling, and then activate in Activated.

activated(){
      window.addEventListener("scroll",this.listenScroll)
      this.loading = false; // Enable infinite scroll},deactivated(){
      window.removeEventListener("scroll",this.listenScroll)
      this.loading = true; // Turn off infinite scroll}Copy the code

You need to reassign false to this.isfixed when entering from another component, otherwise it will remain fixed.

deactivated(){
      window.removeEventListener("scroll",this.listenScroll)
      this.isfixed = false;
      this.loading = true; // Turn off infinite scroll}Copy the code

(3) beforeRouteLeave hook function

We through the document in the beforeRouteLeave. DocumentElement. ScrollTop rolling height

beforeRouteLeave(to,from,next){
      this.homeTop = document.documentElement.scrollTop;
      next();
    }Copy the code

(4) scrollTo method

Add the window.scrollTo method on Activated to record the scrollbar position

activated(){
      window.addEventListener("scroll",this.listenScroll)
      window.scrollTo(0,this.homeTop)
    }Copy the code

Or you can through to the document. The documentElement. ScrollTop assignment for this. HomeTop jump record scrollbar position

document.documentElement.scrollTop = this.homeTopCopy the code

Keep-alive tag cache effect:



12. Return to the top

(1) Custom instruction

We need to add a back to the top component to the home page, and define an isShow parameter in data with v-if command to show and hide the back to the top button. By default, it does not appear at first.

data() {return {
            isShow:false}}Copy the code

Since we added keep-alive to the parent component, we can also use the activated and deactivated methods in the child component, and also add the listenScroll method in the methods, which specifies that the scroll will be displayed when the scroll reaches 200px.

methods:{
        listenScroll() {letSTop = document. DocumentElement. ScrollTop | | document. The body. The scrollTop / / triggered to avoid repetitionif(sTop >= 200 && ! this.isShow){ this.isShow =true;
            }else if(sTop < 200 && this.isShow){
                this.isShow = false; }}},Copy the code

Add and remove operations in two hook functions.

activated(){
        window.addEventListener("scroll",this.listenScroll)
    },
    deactivated(){
        window.removeEventListener("scroll",this.listenScroll)
    }Copy the code

We need to get back to the top by clicking the button, so we need to bind the button to the click event

<div class="back-top-box" v-if = "isShow" @click = "backTop">Copy the code

At the same time, write backTop method in methods

backTop() {window. ScrollTo (0, 0)}Copy the code

However, if the page can also double click the option bar to return to the top, repeated binding events is troublesome, at this time we can write this method as a custom directive, and then add the directive on the tag that needs this function.

That is, encapsulate the custom directive in modules and get the time type from binding.arg. The default is click:

// The v-backtop function can be used to return to the top import Vue from"vue"
Vue.directive("backtop", {bind(el,binding,vnode){
        let eventType = binding.arg || "click"; El.addeventlistener (eventType,e=>{window.scrollto (0,0)})}})Copy the code

Register in main.js

// Import directive import"./modules/directive"Copy the code

Add custom instructions in the button to achieve click back to the top

<div class="back-top-box" v-if = "isShow" v-backtop>Copy the code

Add custom instructions in the options bar, double click to return to the top

<div class = "tab" :class = "{fixedTop:isfixed}" v-backtop:dblclick>Copy the code

Return to top run effect:



13. Movie details page

(1) Dynamic routing

Create the MovieDetail page and configure the dynamic route with: ID

export default {
    name:"moviedetail",
    path:"/moviedetail/:id",
    component:()=>import("@/views/Movie/MovieDetail")}Copy the code

Import, register in index,

import moviedetail from "./moviedetail"Copy the code

Add router-link to the MovieItem and pass the parameter movie.id dynamically

<router-link 
        :to = "{name:'moviedetail',params:{id:movie.id}}"
        tag = "div"
        class = "main_block">Copy the code

Assign the res.data obtained to Movie

getMovie(){
            this.$http.get("/api/db/movie_detail/" + this.$route.params.id).then(res => {
                this.movie = res.data
            })
        }Copy the code

Render the data we need dynamically to the page, then we add a Header component from Mint-UI, assign the title to movie.title, add the return button, and introduce the Header component in main.js

import { Lazyload, InfiniteScroll, Header, Button } from "mint-ui"
Vue.component("mt-header", Header);
Vue.component("mt-button", Button);Copy the code

By inserting the MT-Header tag into the page, you can now click to jump to the movie details page.

<mt-header fixed :title="movie.title">
                <router-link to="/" slot="left">
                    <mt-button icon="back"</mt-button> </router-link> <mt-button icon="more" slot="right"></mt-button>
            </mt-header>Copy the code

Operation effect of detail page:



Shopping cart effect

(1) VuEX official status management tool

Vue is a global state management tool that deals with state sharing among multiple components in a project

Vuex is an official vUE state management tool. What is state? We have a concept in front-end development: data-driven, any display difference on the page should be controlled by a piece of data, and this piece of data is also called state.

In vue. Data transfer and communication between components are frequent, and communication between parent and non-parent components is quite complete. However, the only difficulty is data sharing between multiple components, which is handled by VUEX.

Next we need to implement a shopping cart effect by configuring a bottom navigation bar:

Two more components are created in the Mine component, one is the shopping cart car page and the other is the shopping list list page. In the mine route, the secondary route is configured.

// Configure children:[{path:"",redirect:"list"},
        {path:"list",component:()=>import("@/views/Mine/List"),name:"list"},
        {path:"car",component:()=>import("@/views/Mine/Car"),name:"car"},]Copy the code

In mine, router-view was used to display the secondary component page, tabbar bottom navigation component in Mint-UI was introduced, and registered in main.js.

Import {Lazyload, InfiniteScroll, Header, Button, Tabbar, TabItem} from"mint-ui"
Vue.component("mt-tabbar", Tabbar);
Vue.component("mt-tab-item", TabItem);Copy the code

Reference in the mine page, dynamically load item data to the page, and add router-link to realize the click-to-jump function

<mt-tabbar>
           <mt-tab-item
            v-for="nav in navs"
            :key="nav.id"
           >
            <router-link :to = "{name:nav.name}" active-class = "active">
              <img :src = "nav.src">
              {{nav.title}}
            </router-link>
           </mt-tab-item>
 </mt-tabbar>Copy the code

You can install the Vue Devtools plug-in to view the status of vuex





(2) Creation of VUEX

  1. Creating a store:
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

import state from "./state"
import getters from "./getters"
import mutations from "./mutations"
import actions from "./actions"
export default new Vuex.Store({
    state,
    getters,
    mutations,
    actions 
})Copy the code
  1. Set up the state

State is a pure object with some state mount on it, declaring a shared piece of data

exportDefault {num:0}Copy the code
  1. Configure store in the root instance

This way, we can use the store API in any component through this.$store



  1. Use state in components

Store can be accessed in the component via this.$store

So we can also use the data managed in state with this.$store.state and write it in data

data() {return{
            num:this.$store.state.num
        }
    }Copy the code

The data can then be retrieved by {{num}}

However, we found that when used this way, the VUE component does not re-render when the state data changes

That is, if you want to use it in a responsive manner in a component, you need to use it through computed attributes

computed:{
    num() {return this.$store.state.num
    }
}Copy the code

Vuex provides a mapState helper function to retrieve and use the state stored in vuex’s store in components, but mapState is also an object, and objects cannot be nested

computed:{
        mapState({
            num:state => state.num
        })
    }Copy the code

So we can write this:

computed:mapState(["num"])Copy the code

Or use mapState… A:

computed:{ ... mapState({ num:state => state.num }) }Copy the code

If num exists in the component and num is the name of the state, it will cause a conflict. If num is the name of the state, it will cause a conflict.

computed:{ ... MapState ({_num:state => state.num // Equivalent to this.$store.state.num来获取num的方法
        })
    }Copy the code
  1. getters

Sometimes we need to derive a new state from one of the states in state. For example, if we have num in state and need a state twice as large as num in some components, we can use getters to create a new state.

export default{
    doubleNum(state){
        return state.num*2
    }
}Copy the code

Once created, use this.$store.getters. DoubleNum to retrieve the data in the component

Of course, Vuex also provides mapGetters helper functions to help us use getters state in the component, and use the same method as mapState, expand after assigning.

import {mapState,mapGetters} from "vuex"
exportdefault { computed:{ ... MapState ({_num:state => state.num // Equivalent to this.$store.state.num = num}),... mapGetters(["doubleNum"])}Copy the code

Write the method name in double parentheses

{{doubleNum}}Copy the code
  1. Use mutations to change state

Instead of changing state directly in the component: this.$store.state.num=2, we need to change it using Mutations, which is also a pure object that contains many methods for changing state.

export default{
    changeNum(state) {
        state.num++
    }
}Copy the code

The parameters of these methods receive state and change in the function body, and the data used by the component changes as well, making it reactive.

However, the method of mutations cannot be called directly, instead, it needs to be called using this. codestore.mit. The first parameter is the name of the method to be called, and the second parameter is the pass parameter.

methods:{
        changeNum(){
            this.$store.commit("changeNum")}}Copy the code

Vuex provides the mapMutations method to help us call the mutations method in the component, using the same method as mapState and mapGetters, but it should be written in methods.

methods:{ ... mapMutations(["changeNum"])}Copy the code
  1. Define the method name as a constant

To prevent the method name from being changed, we typically define it in a separate const.js file to be imported when used

import { CHANGE_NUM} from "./const"
export default{
    CHANGE_NUM(state) {
        state.num++
    }
}Copy the code

When a constant’s name changes, it does not change at the same time. Therefore, we need to wrap a bracket around it to make it a variable [CHANGE_NUM] for easy maintenance and management.

// import {CHANGE_NUM} from"@/store/const"// register methods:{... MapMutations ([CHANGE_NUM])} < button@click ="CHANGE_NUM"> click on NUM! </button>Copy the code
  1. Use actions for asynchronous operations

Actions are similar to mutation, except that they commit mutation rather than directly changing the state. Actions can contain any asynchronous operation.

That is, if there is a requirement to change the state after an asynchronous processing, we should call actions in the component first for asynchronous action and then actions for mutation to change the data

import { RANDOM_NUM } from "./const"
exportDefault {getNumFromBackend(store){//actions Generally make an asynchronous request to obtain data, and then send mutations in the specific method to change the statussetTimeout(()=>{// Get a random value of 100letRandomNum = math.floor (math.random ()*100) store.com MIT (RANDOM_NUM, randomNum) To change the value of state},1000)}}Copy the code

Mutations receive a random number from the actions and assign it to the state

import { CHANGE_NUM, RANDOM_NUM} from "./const"
exportDefault {[CHANGE_NUM](state) {state.num++}, [RANDOM_NUM](state,randomNum){state.num = randomNum}}Copy the code

Click button on the front page to trigger the event

<button @click = "getRandom"> < span style = "max-width: 100%; </button>Copy the code

After the state changes, execute render and re-render the view to get the latest state

getRandom(){// issue action this.$store.dispatch("getNumFromBackend")}Copy the code

Call the actions method in the component via the this.$store.dispatch method

You can also use mapActions to help

. mapActions(["getNumFromBackend"])Copy the code

To do this, simply write the method name in actions

<button @click = "getNumFromBackend"> < span style = "max-width: 100%; </button>Copy the code
  1. Use modules for modular partitioning

When there is a lot of data information, we will put the files belonging to the same module into a modules for management, create the myNum folder, and directly put actions, const, getters, mutations, and state into myNum for management.

Export each module by creating an index.js file in the myNum file

import state from "./state"
import mutations from "./mutations"
import actions from "./actions"
import getters from "./getters"
export default{
    state,mutations,actions,getters
}Copy the code

Reintroduce in index.js

import myNum from "./myNum"
export default new Vuex.Store({
    modules:{
     myNum
    }
})Copy the code

Now we will change the reference path in the front page

import {CHANGE_NUM} from "@/store/myNum/const"Copy the code

We can’t get num from state.num



We need to nest another layer of myNum on the outside

. mapState({ _num:state => state.myNum.num })Copy the code

(3) Now let’s implement a shopping cart effect

First create the myCar folder in store, one by one

  1. State. js: stores shared data

  2. Actions. js: Make asynchronous requests

  3. Const. Js: constant name

  4. Mutations. Js: Methods for changing status

  5. Getters: Dispatches a new state based on a state

  6. Index. js: summary

These files are introduced in order in index.js

import state from "./state"
import mutations from "./mutations"
import actions from "./actions"
import getters from "./getters"
export default{
    state,mutations,actions,getters
}Copy the code

Introduce myCar index in store/index.js

import myNum from "./myNum"
import myCar from "./myCar"
export default new Vuex.Store({
    modules:{
        myNum,
        myCar
    }
})Copy the code

Define an empty array cars in state.js

exportDefault {cars:[]// declare a shared data}Copy the code

In this way, we can get the data of CARS in state, which is empty by default.



Here we use the Cell component in Mint-UI to build the page that hosts each purchase. Register in main.js

import { Lazyload, InfiniteScroll, Header, Button, Tabbar, TabItem,Cell} from "mint-ui"
Vue.component("mt-cell", Cell);Copy the code

Then we can use it through MT-Cell

<mt-cell
            title = 'Title text'
            value = 'Link with link'
            label = "Description"
        >
        <img slot = "icon" src = "" alt = "">
        </mt-cell>Copy the code

Put the goods data we obtained into the goods.json API in the public static folder

{
    "dealList":[
        {
            "firstTitle": "Single"."title":"1 bucket 46oz Plain Popcorn + 1 cup 22oz Coke"."price": 33,
            "dealId": 100154273,
            "imageUrl":"https://p0.meituan.net/movie/5c30ed6dc1e3b99345c18454f69c4582176824.jpg@388w_388h_1e_1c"."curNumberDesc": "Sold 379"
        },
        {
            "firstTitle": "Single"."title": "1 bucket 46oz Plain Popcorn + 1 cup 22oz Sprite"."price": 33,
            "dealId": 100223426,
            "imageUrl": "https://p0.meituan.net/movie/5c30ed6dc1e3b99345c18454f69c4582176824.jpg@388w_388h_1e_1c"."curNumberDesc": "Sold 12"
        },
        {
            "firstTitle": "Single"."title": "Imported food 1 portion"."price": 8.89."dealId": 100212615,
            "imageUrl": "https://p1.meituan.net/movie/21f1d203838577db9ef915b980867acc203978.jpg@750w_750h_1e_1c"."curNumberDesc": "Sold 8"
        },
        {
            "firstTitle": "Double"."title": "1 tub of 85oz Plain Popcorn +2 cups of 22oz Cola"."price": 44,
            "dealId": 100152611,
            "imageUrl": "https://p0.meituan.net/movie/bf014964c24ca2ef107133eaed75a6e5191344.jpg@388w_388h_1e_1c"."curNumberDesc": "Sold 647"
        },
        {
            "firstTitle": "Double"."title": "1 tub 85oz Plain Popcorn +2 cups 22oz Sprite"."price": 44,
            "dealId": 100223425,
            "imageUrl": "https://p0.meituan.net/movie/bf014964c24ca2ef107133eaed75a6e5191344.jpg@388w_388h_1e_1c"."curNumberDesc": "Sold 6"
        },
        {
            "firstTitle": "Many people"."title": "1 tub 85oz Plain Popcorn +2 cups 22oz Cola + 1 bottle Ice Age Water"."price": 55."dealId": 100152612,
            "imageUrl": "https://p1.meituan.net/movie/c89df7bf2b1b02cbb326b06ecbbf1ddf203619.jpg@388w_388h_1e_1c"."curNumberDesc": "Sold 89"}}]Copy the code

We’re going to declare a data data store.

data() {return{
            goods:[],
        }
    }Copy the code

Define method getGoods to request data asynchronously

methods:{
        getGoods(){
            this.$http.get("/api/goods.json").then(res=>{
                console.log(res)
            })
        }
    },Copy the code

Call it in the lifecycle hook function

created(){
        this.getGoods()
    }Copy the code

You can take the data on the RES and assign it to goods

getGoods(){
            this.$http.get("/api/goods.json").then(res=>{ // console.log(res) this.goods = res.data.dealList; })}Copy the code

Loop rendering in MT-cell

<mt-cell
            :title="good.title"
            :label="' $' + good. Price"
            v-for="good in goods" 
            :key="good.dealId"
        >
        <mt-button type = "danger" size = "small" @click = "addGoodInCar(good)"</mt-button> <div class ="firstTitle">{{good.firstTitle}}</div>
        <img width ="70" height = "70" slot="icon" :src="good.imageUrl" alt="">
        </mt-cell>Copy the code

The real request for shopping cart information should be made asynchronously from the background. Now we rely on LocalStorage as the background to simulate the background database and store our cars data.

function getCar() {return JSON.parse(localStorage.cars ? localStorage.cars : "[]")}Copy the code

Now write the method addGoodInCar in action.js to add an item to the cart

AddGoodInCar (store,goodInfo)setTimeout(()=>{// Get the cart returned from the backgroundletcars = getCar(); / / ({})letIsHas = cars.some(item => {// Check whether the cart has this itemif(item.dealId === goodInfo. DealId){// If the same item is added, the number of items is ++return true;}
            })
            if(! Goodinfo. num = 1; Cars. push(goodInfo)// Add items to cars data} // Notify background to change carslocalComroad.cars = json.cars (cars); comroadroad.cars (cars); comroadroad.cars (cars);Copy the code

Declare a constant in const

const SYNC_UPDATE = "SYNC_UPDATE"
export { SYNC_UPDATE }Copy the code

Mutations defines a method for updating data

import { SYNC_UPDATE} from "./const"
exportdefault{ [SYNC_UPDATE](state,newCar){ state.cars = newCar; }}Copy the code

Call this method in actions

store.commit(SYNC_UPDATE, cars)Copy the code

Call this method in the front page

import {mapActions} from "vuex"methods:{ ... mapActions(["addGoodInCar"])}Copy the code

Add the click event to the Mt-button and pass the good argument

<mt-button type = "danger" size = "small" @click = "addGoodInCar(good)"> buy < / mt - button >Copy the code

Click Button on the page, after a second, cars will get a product information, click again, num attribute ++, become 2.



Write a method to get the shopping cart in actions

InitCar (store){// Get shopping cartlet cars = getCar()
        store.commit(SYNC_UPDATE,cars)
    }Copy the code

Called from the Created hook function in global app.js so that other components can access the cars data, and the data shared by multiple components is managed in VUex.

created(){// make the page go to the front page, triggering the router.beforeEach function in the Header, otherwise the Header will repeat this.$router.push("/") // Initialize the shopping cart this.$store.dispatch("initCar")}Copy the code

With a reference to list, start writing a car.vue page that doesn’t need to request data, just displays it, aided by mapState to display the data in state.

import {mapState} from "vuex"
exportdefault { computed:{ ... mapState({ cars:state=>state.myCar.cars }) } }Copy the code

The V-for loop above takes the data directly from the CARS

v-for="good in cars" Copy the code

Change button to “+” and “-“

<mt-button type = "danger" size = "small" @click = "addGoodInCar(good)">+</mt-button>
<mt-button type = "danger" size = "small" @click = "addGoodInCar(good)">-</mt-button>Copy the code

Introduce the addGoodInCar method

import {mapState,mapActions} from "vuex"
exportdefault { computed:{ ... mapState({ cars:state=>state.myCar.cars }) }, methods:{ ... mapActions(["addGoodInCar"]])}}Copy the code

Write methods to reduce items in actions

ReduceGoodInCar (store,goodInfo){// Get the shopping cart returned from the backgroundlet cars = getCar();
        cars = cars.filter(item=>{
            if (item.dealId === goodInfo.dealId){
                item.num--
            }
            return true; }) // Notify the background to change the carslocalCom.cars = json.cars (cars); com.cars = com.cars (cars)Copy the code

Add a judgment after item–, return false if the number is less than or equal to zero to prevent subsequent operations

if(item.num<=0) return falseCopy the code

Next, we will add a function to calculate the total price on the page. Since we will calculate the total price depending on the change of quantity and unit price, we will write this method in getters

export default{
    computedTotal(state){
        letcars = state.cars; // Cars can be accessed directly in the same moduleletCars. forEach(item=>{total.price += item.price * item.num; Num += item.num += item.num})returnTotal // Returns the total object}}Copy the code

A computedTotal method is introduced with mapGetters assistance

import {mapState,mapActions,mapGetters} from "vuex"
exportdefault { computed:{ ... mapState({ cars:state=>state.myCar.cars }), ... mapGetters(["computedTotal"]) }, methods:{ ... mapActions(["addGoodInCar"."reduceGoodInCar"])}}Copy the code

Click to increase the number of goods, and we will find that the total number sometimes has many decimal places, so we need to process the data of the obtained price

Total.price = total.price. ToFixed (2)// Round up and keep two decimal placesCopy the code

On the home page, we control the display and hiding of commodity information through V-if and V-else. When there is no commodity, a P label is displayed to remind the user that there is no commodity, and a router-link is provided to jump back to the commodity list page.

<p v-if = "cars.length === 0"> No more items <router-link to ="/mine/list"</router-link> </p> <div V-else > < Mt-cell :title="good.title"
                :label="' $' + good price + '*' + good. Num." " 
                v-for="good in cars" 
                :key="good.dealId"
            >
            <mt-button type = "danger" size = "small" @click = "addGoodInCar(good)">+</mt-button>
            <mt-button type = "danger" size = "small" @click = "reduceGoodInCar(good)">-</mt-button>
            <div class = "firstTitle">{{good.firstTitle}}</div>
            <img width ="70" height = "70" slot="icon" :src="good.imageUrl" alt="">
            </mt-cell>
        </div>Copy the code

Shopping cart running effect:



15. Package online

(1) Modify the configuration file

Go to the vue-config. js configuration file for your project and change publicPath: to ‘/ V-douban /’ in module.exports.



The path of the local request also requires/V-douban



(2) Package files

Run yarn Build to package the dist file

(3) Connect to the FTP server and modify nginx

Go to the /usr/local/nginx/conf directory and transfer the nginx.config file to the local directory.





Modify the nginx.config file to configure the data interface proxy.

The location/API/db {proxy_pass http://47.96.0.211:9000/db; } the location/data/my {proxy_pass http://118.31.109.254:8088/my; } the location/douban/my {proxy_pass http://47.111.166.60:9000/my; }Copy the code

Upload the new nginx.config file to the server, overwriting the original file.



Connect to the database on the terminal and restart the Nginx server.

./nginx -s reloadCopy the code



Since our configuration uses the HTTPS path, we need to enable SSL

Check out this article:

www.cnblogs.com/piscesLoveC…

Check whether the configuration file is updated after installation:



Go to the /usr/local/nginx/html directory to create a V-douban folder



Upload all the files in the packaged Dist folder to the server



After completion of transmission, you can visit the online project http://39.96.84.220/v-douban on the web page