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
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
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:
-
To determine whether to configure the route in history mode, enter n and press Enter
-
Since we have just chosen the CSS preprocessor language, we will choose the stable version of Sass/SCSS (with Node-sass)
-
Where do you need to store your configuration files, let’s say in package.json
-
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
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 browser
XMLHttpRequests
- From the node. Js to create
http
request - Supporting Promise API
- Intercept requests and responses
- Transform request data and response data
- Cancel the request
- Automatically convert JSON data
- The client supports defense
XSRF
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
(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
Cannot be used as global variables, only in real time
.
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
- 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
- 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
- Configure store in the root instance
This way, we can use the store API in any component through this.$store
- 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
- 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
- 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
- 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
- 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
- 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
-
State. js: stores shared data
-
Actions. js: Make asynchronous requests
-
Const. Js: constant name
-
Mutations. Js: Methods for changing status
-
Getters: Dispatches a new state based on a state
-
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