Building vue--- shopping cart case from scratch

Posted by davey10101 on Wed, 02 Feb 2022 12:28:33 +0100

Shopping cart case

1. Case effect

 2. Implementation steps

  • ① Initialize project basic structure
  • ② Encapsulating EsHeader components
  • ③ Requesting product list data based on axios
  • ④ Encapsulate EsFooter components
  • ⑤ Encapsulate EsGoods components
  • ⑥ Encapsulate the EsCounter component

1. Initialize project structure

Run the following command to initialize the vite project:

npm init vite-app code-cart
cd code-cart
npm install

2. Clean up the project structure:

  • Put the bootstrap related files into the src/assets directory
  • In main JS css
  • Empty app Vue component
  • Delete HelloWorld. In the components directory Vue component

3. Enable less syntax for component style

npm i less -D

4. Initialize index The CSS global style is as follows:

:root {
 font-size: 12px;
 } 

2. Encapsulate the ES header component

2.1 create and register EsHeader component

1. In src/components/es-header/ New under directory EsHeader.vue Components:
<template>
 <div>EsHeader assembly</div>
</template> <script>
export default {
 name: 'EsHeader', }
</script> <style lang="less" scoped></style>
2. In App.vue Import and register in component EsHeader.vue Components:
// Import header component
import EsHeader from './components/es-header/EsHeader.vue'
export default {
 name: 'MyApp',
 components: {
 // Register header component
 EsHeader,
 },
} 
3. In App.vue of template Used in template structure EsHeader Components:\
<template>
 <div>
 <h1>App Root component</h1>
 <!-- use es-header assembly -->
 <es-header></es-header>
 </div>
</template> 
2.2 encapsulation es-header assembly
0. Packaging requirements:
  • Allow users to customize the title content
  • Allow users to customize color text color
  • Allow users to customize bgcolor background color
  • Allow users to customize fsize font size
  • The ES header component must be fixed at the top of the page, with a height of 45px, the text centered, and the z-index of 999

1. In es-header The following are encapsulated in the component props Properties:
export default {
 name: 'EsHeader',
 props: {
 title: { // Title Content
 type: String,
 default: 'es-header',
 },
 bgcolor: { // background color 
 type: String,
 default: '#007BFF',
 },
 color: { // Text color
 type: String,
 default: '#ffffff',
 },
 fsize: { // Text size
 type: Number,
 default: 12,
 },
 },
}
2. Render the title content and dynamically change it into DOM Within the element binding line style Style object:
<template>
 <div :style="{ color: color, backgroundColor: bgcolor, fontSize:
fsize + 'px' }">{{ title }}</div>
</template> 
3. DOM Node addition header-container Class name, further beautification es-header Style of component:
<template> 
 <div class="header-container" :style="{ color: color,
backgroundColor: bgcolor, fontSize: fsize + 'px' }">
 {{ title }}
 </div>
</template> <style lang="less" scoped>
.header-container {
 height: 45px;
 line-height: 45px;
 text-align: center;
 position: fixed;
 top: 0;
 left: 0;
 width: 100%;
 z-index: 999;
}
</style> 
4. stay App Used in the root component es-header Components, through title attribute appoint Title Content
<template>
 <div class="app-container">
 <h1>App Root component</h1>
 
 <!-- by es-header Component assignment title The value of the property -->
 <es-header title="Shopping cart case"></es-header>
 </div>
</template>

3. Request product list data based on axios

1. Run the following command to install axios

npm i axios -S
2. In main.js Import and global configuration from the portal file axios :
import { createApp } from 'vue'
import App from './App.vue'
import './assets/css/bootstrap.css'
import './index.css'
// Import axios
import axios from 'axios'
const app = createApp(App)
// Configure the root path of the request
axios.defaults.baseURL = 'https://www.escook.cn'
// Mount axios as a global $http custom attribute
app.config.globalProperties.$http = axios
app.mount('#app') 
3.2 Request item list data
1. In App.vue The following is declared in the root component data Data:
data() {
 return {
 // Item list data
 goodslist: [],
 }
},
2. In App.vue Root component created In the life cycle function, Pre call Get product list data methods method
/ The lifecycle function after the component instance is created
created() {
 // Call the getGoodsList method in methods to request the data of the commodity list
 this.getGoodsList()
},
3. stay Ap.vue Root component methods Node, declare the pre called getGoodsList method:
methods: {
 // Request data for item list
 async getGoodsList() {
 // 1. Access the $http attribute of the global mount through the component instance this and initiate
Ajax Data request
 const { data: res } = await this.$http.get('/api/cart')
 // 2. Judge whether the request is successful
 if (res.status !== 200) return alert('Failed to request commodity list data!')
 // 3. Store the requested data in data for use during page rendering
 this.goodslist = res.list
 },
},

4. Package es footer components

Create and register esoter component 1.4

1. In src/components/es-footer/ New under directory EsFooter.vue Components:
<template>
 <div>EsFooter assembly</div>
</template> <script>
export default {
 name: 'EsFooter', }
</script> <style lang="less" scoped></style>
2. In App.vue Import and register in component EsFooter.vue Components:
// Import header component
import EsHeader from './components/es-header/EsHeader.vue'
// Import footer component
import EsFooter from './components/es-footer/EsFooter.vue'
export default {
 name: 'MyApp',
 components: {
 // Register header component
 EsHeader,
 // Register footer component
 EsFooter,
 },
} 6
3. stay App.vue of template Used in template structure EsFooter Components:
<template>
 <div>
 <h1>App Root component</h1>
 <!-- use es-header assembly -->
 <es-header></es-header>
 <!-- use es-footer assembly -->
 <es-footer></es-footer>
 </div>
</template> 
4.2 encapsulation es-footer assembly
  • The ES footer component must be fixed and positioned at the bottom of the page. The height is 50px. The two ends of the content are aligned with each other. Z-index: 999
  • Allows users to customize the total amount price (in Yuan) and keep two decimal places during rendering
  • Allows the user to customize the total quantity and render it to the settlement button. If the number of items to be settled is 0 , the settlement button is disabled
  • Allows users to customize the selected state of the isfull select all button
  • Allows the user to listen to the change of the selected state of the select all button in the form of user-defined events and obtain the latest selected state
<!-- Footer assembly -->
<my-footer :isfull="false" :total="1" :amount="98"
@fullChange="onFullStateChange"></my-footer>
4.2.1 Basic layout of rendering components
1. Will EsFooter.vue Components are fixed at the bottom of the page
<template>
 <div class="footer-container">EsFooter assembly</div>
</template> <script>
export default {
 name: 'EsFooter', }
</script> <style lang="less" scoped>
.footer-container {
 // Set width and height
 height: 50px;
 width: 100%;
 // Set the background color and top border color
 background-color: white;
 border-top: 1px solid #efefef;
 // Bottom fixed positioning
 position: fixed;
 bottom: 0;
 left: 0;
 // Alignment of internal elements
 display: flex;
 justify-content: space-between;
 align-items: center;
 // Set left and right padding
 padding: 0 10px; }
</style>
2. According to bootstrap Provided Checkboxes https://v4.bootcss.com/docs/components/forms/#check
boxes Render left Select all Button:
<template>
 <div class="footer-container">
 <!-- Select all button -->
 <div class="custom-control custom-checkbox">
 <input type="checkbox" class="custom-control-input"
id="fullCheck" />
 <label class="custom-control-label" for="fullCheck">Select all
</label>
 </div>
 </div>
</template> 
And in the global style sheet index.css Medium coverage Select all Button fillet style:
.custom-checkbox .custom-control-label::before {
 border-radius: 10px; } 
3. Render total Corresponding price area:
<template>
 <div class="footer-container">
 <!-- Select all button -->
 <div class="custom-control custom-checkbox">
 <input type="checkbox" class="custom-control-input"
id="fullCheck" />
 <label class="custom-control-label" for="fullCheck">Select all
</label>
 </div>
 <!-- total -->
 <div>
 <span>total:</span>
 <span class="amount">¥0.00</span>
 </div>
 </div>
</template>
And in the current component <style> The style of beautifying the total price in the node
.amount {
 color: red;
 font-weight: bold; }
4. According to Rendering provided by bootstrap Settlement button :
Buttons https://v4.bootcss.com/docs/components/buttons/#examples
<template>
 <div class="footer-container">
 <!-- Select all button -->
 <div class="custom-control custom-checkbox">
 <input type="checkbox" class="custom-control-input"
id="fullCheck" />
 <label class="custom-control-label" for="fullCheck">Select all
</label>
 </div>
 <!-- total -->
 <div>
 <span>total:</span>
 <span class="amount">¥0.00</span>
 </div>
 <!-- Settlement button -->
 <button type="button" class="btn btn-primary">settlement(0)</button>
 </div>
</template> 
And in the current component <style> The style of the settlement button in the node
.btn-primary {
 // Set fixed height
 height: 38px;
 // Set fillet effect
 border-radius: 19px;
 // Set minimum width
 min-width: 90px; }
4.2.2 Encapsulate custom attributes amount
amount Is the total price of the checked item
1. In EsFooter.vue Component props Node, declare the following custom attributes
export default {
 name: 'EsFooter',
 props: {
 // Total price of the checked item
 amount: {
 type: Number,
 default: 0,
 },
 },
}
2. In EsFooter.vue Component DOM Rendering in structure amount Value of:
<!-- total -->
<div>
 <span>total:</span>
 <!-- take amount Keep two decimal places for the value of -->
 <span class="amount">¥{{ amount.toFixed(2) }}</span>
</div> 
4.2.3 Encapsulate custom attributes total
total Is the total quantity of the checked item
1. stay EsFooter.vue Component props Node, declare the following custom attributes
export default {
 name: 'EsFooter',
 props: {
 // Total price of the checked item
 amount: {
 type: Number,
 default: 0,
 },
 // Total quantity of checked items
 total: {
 type: Number,
 default: 0,
 },
 },
}
2. In EsFooter.vue Component DOM Rendering in structure total Value of
<!-- Settlement button -->
<button type="button" class="btn btn-primary">settlement({{total}})
</button>
3. Dynamic control settlement button Disabled status of:
<!-- disabled The value of is true,Indicates that the button is disabled -->
<button type="button" class="btn btn-primary" :disabled="total ===
0">settlement({{ total }})</button>
4.2.4 Encapsulate custom attributes isfull
isfull Is the selected state of the select all button, true Indicates selected, false Indicates that it is not selected
1. stay EsFooter.vue Component props Node, declare the following custom attributes:
export default {
 name: 'EsFooter',
 props: {
 // Total price of the checked item
 amount: {
 type: Number,
 default: 0,
 },
 // Total quantity of checked items
 total: {
 type: Number,
 default: 0,
 },
 // The selected state of the select all button
 isfull: {
 type: Boolean,
 default: false,
 },
 },
}

2. Dynamically bind check boxes ckecked Value of property:
<!-- Select all button -->
<div class="custom-control custom-checkbox">
 <input type="checkbox" class="custom-control-input"
id="fullCheck" :checked="isfull" />
 <label class="custom-control-label" for="fullCheck">Select all</label>
</div> 
4.2.5 Encapsulate custom event fullChange
By customizing events fullChange , pass the latest selected status to the user of the component
1. Select the status change in the monitor check box change event:
<!-- Select all button -->
<div class="custom-control custom-checkbox">
 <input type="checkbox" class="custom-control-input"
id="fullCheck" :checked="isfull" @change="onCheckBoxChange" />
 <label class="custom-control-label" for="fullCheck">Select all</label>
</div> 
2. In methods Statement in onCheckBoxChange And through the event object e Get the latest selected status:
methods: {
 // Listen for changes in the selected state of the check box
 onCheckBoxChange(e) {
 // e.target.checked is the latest selected state of the check box
 console.log(e.target.checked)
 },
},
3. In emits Custom events declared in:
// Declare custom events
emits: ['fullChange'],
stay onCheckBoxChange In the event handler function, through $emit() Trigger a custom event and select the latest one
The status is passed to the user of the current component:
methods: {
 onCheckBoxChange(e) {
 // Trigger custom event
 this.$emit('fullChange', e.target.checked)
 },
},
5. stay App.vue Test in root component EsFooter.vue Components:
<!-- use footer assembly -->
<es-footer :total="0" :amount="0" @fullChange="onFullStateChange">
</es-footer>
And in methods Statement in onFullStateChange Processing function, obtained through formal parameters Select all button abreast of the times
Selected status:
methods: {
 // Monitor the change of the state of the select all button
 onFullStateChange(isFull) {
 // Print the latest selected status of the select all button
 console.log(isFull)
 },
},

5. Package es goods components

5.1 create and register EsGoods components

1. stay src/components/es-goods/ New under directory EsGoods.vue Components:
<template>
 <div>EsGoods assembly</div>
</template> <script>
export default {
 name: 'EsGoods', }
</script> <style lang="less" scoped></style>
2. In App.vue Import and register in component EsGoods.vue Components:
// Import header component
import EsHeader from './components/es-header/EsHeader.vue'
// Import footer component
import EsFooter from './components/es-footer/EsFooter.vue'
// Import goods component
import EsGoods from './components/es-goods/EsGoods.vue'
export default {
 name: 'MyApp',
 components: {
 // Register header component
 EsHeader,
 // Register footer component
 EsFooter,
 // Register the goods component
 EsGoods,
 },
}
3. In App.vue of template Used in template structure EsGoods assembly
<template>
 <div class="app-container">
 <!-- use header assembly -->
 <es-header title="Shopping cart case"></es-header>
 <!-- use goods assembly -->
 <es-goods></es-goods>
 <!-- use footer assembly -->
 <es-footer :total="0" :amount="0"
@fullChange="onFullStateChange"></es-footer>
 </div>
</template> 
5.2 encapsulation es-goods assembly
5.2.0 Packaging requirements
  • 1. Implement the basic layout of EsGoods components
  • 2. Encapsulate the six custom attributes of the component (id, thumb, title, price, count, checked)
  • 3. Encapsulate the custom event stateChange of the component, allowing the outside world to listen to the change of the selected state of the component
<!-- use goods assembly -->
<es-goods
 v-for="item in goodslist"
 :key="item.id"
 :id="item.id"
 :thumb="item.goods_img"
 :title="item.goods_name"
 :price="item.goods_price"
 :count="item.goods_count"
 :checked="item.goods_state"
 @stateChange="onGoodsStateChange"
></es-goods> 
5.2.1 Basic layout of rendering components
1. Rendering EsGoods Foundation of components DOM Structure:
<template>
  <div class="goods-container">
    <!-- Left picture area -->
    <div class="left">
      <!-- Thumbnail of the item -->
      <img src="" alt="Product picture" class="thumb" />
    </div>
    <!-- Right information area -->
    <div class="right">
      <!-- Trade name -->
      <div class="top">xxxx</div>
      <div class="bottom">
        <!-- commodity price -->
        <div class="price">¥0.00</div>
        <!-- Quantity of goods -->
        <div class="count">quantity</div>
      </div>
    </div>
  </div>
</template> 
2. Layout style of beautifying components
.goods-container {
  display: flex;
  padding: 50px,10px;
  // The style of the picture on the left
  .left {
    margin-right: 10px;
    // Product picture
    .thumb {
      display: block;
      width: 100px;
      height: 100px;
      background-color: #efefef;
    }
  }
  // Style of commodity name, unit price and quantity on the right
  .right {
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    flex: 1;
    .top {
      font-weight: bold;
    }
    .bottom {
      display: flex;
      justify-content: space-between;
      align-items: center;
      .price {
        color: red;
        font-weight: bold;
      }
    }
  }
}


Package outside product thumbnail check box ( https://v4.bootcss.com/docs/components/forms/#checkboxes )
<!-- Left picture and check box area -->
<div class="left">
 <!-- check box -->
 <div class="custom-control custom-checkbox">
 <input type="checkbox" class="custom-control-input"
id="customCheck1" />
 <!-- Wrap the product picture in label Click the picture to switch the selection of "check box"
Medium state -->
 <label class="custom-control-label" for="customCheck1">
 <img src="" alt="Product picture" class="thumb" />
 </label>
 </div>
 <!-- <img src="" alt="Product picture" class="thumb" /> -->
</div> 
4. cover check box Default style for:
.custom-control-label::before,
.custom-control-label::after {
 top: 3.4rem; }
5. In App.vue Loop rendering in component EsGoods.vue assembly
<!-- use goods assembly -->
<es-goods v-for="item in goodslist" :key="item.id"></es-goods> 12
6. For EsGoods.vue Add top border:
.goods-container {
 display: flex;
 padding: 10px;
 // The resulting selector is goods-container + .goods-container
 // In css, (+) is the "adjacent sibling selector", which means that after selecting another element immediately connected
 Element with the same parent element.
 + .goods-container {
 border-top: 1px solid #efefef;
 }
 // ... Omit other styles
}
5.2.2 Encapsulate custom attributes id
id Is a unique identifier for each item
1. In EsGoods.vue Component props Node, declare the following custom attributes:
export default {
 name: 'EsGoods',
 props: {
 // Unique key value
 id: {
 type: [String, Number], // The value of id can be "string" or "numeric"
 required: true,
 },
 },
}
2. Bind dynamically when rendering check boxes input of id And properties label of for Attribute value
<!-- check box -->
<div class="custom-control custom-checkbox">
 <input type="checkbox" class="custom-control-input" :id="id" />
 <label class="custom-control-label" :for="id">
 <img src="" alt="Product picture" class="thumb" />
 </label>
</div> 
3. stay App.vue Used in EsGoods.vue Component, dynamic binding id Value of property:
<!-- use goods assembly -->
<es-goods v-for="item in goodslist" :id="item.id"></es-goods> 12
5.2.3 Encapsulate other attributes
except id In addition to attributes, EsGoods Components also need to be encapsulated:
thumbnail ( thumb ), Trade name ( title ), Unit Price ( price ), quantity ( count ), Check status
( checked )This 5 Attributes
1. stay EsGoods.vue Component props Node, declare the following custom attributes:
export default {
 name: 'EsGoods',
 props: {
 // Unique key value
 id: {
 type: [String, Number],
 required: true,
 },
 // 1. Thumbnail of goods
 thumb: {
 type: String,
 required: true,
 },
 // 2. Name of commodity
 title: {
 type: String,
 required: true,
 },
 // 3. Unit price
 price: {
type: Number,
 required: true,
 },
 // 4. Quantity
 count: {
 type: Number,
 required: true,
 },
 // 5. Check status of goods
 checked: {
 type: Boolean,
 required: true,
 },
 },
}
2. stay EsGoods.vue Component DOM Information data of rendered goods in the structure:
<template>
 <div class="goods-container">
 <!-- Left picture and check box area -->
 <div class="left">
 <!-- check box -->
 <div class="custom-control custom-checkbox">
 <input type="checkbox" class="custom-control-input"
:id="id" :checked="checked" />
 <label class="custom-control-label" :for="id">
 <img :src="thumb" alt="Product picture" class="thumb" />
 </label>
 </div>
 </div>
 <!-- Right information area -->
 <div class="right">
 <!-- Trade name -->
 <div class="top">{{ title }}</div>
 <div class="bottom">
 <!-- commodity price -->
 <div class="price">¥{{ price.toFixed(2) }}</div>
 <!-- Quantity of goods -->
 <div class="count">number:{{ count }}</div>
 </div>
 </div>
 </div> 
</template> 
stay App.vue Used in components EsGoods.vue When the component is, the value of the corresponding property is dynamically bound:
<!-- use goods assembly -->
<es-goods
 v-for="item in goodslist"
 :key="item.id"
 :id="item.id"
 :thumb="item.goods_img"
 :title="item.goods_name"
 :price="item.goods_price"
 :count="item.goods_count"
 :checked="item.goods_state"
></es-goods> 123456789
10
11
5.2.4 Encapsulate custom events stateChange
Click the check box When, you can Latest tick status , pass Custom event To the user of the component
1. In EsGoods.vue Component, listening checkbox Select event with status change:
<!-- Of the listen check box change event -->
<input type="checkbox" class="custom-control-input" :id="id"
:checked="checked" @change="onCheckBoxChange" />
2. In EsGoods.vue Component methods Declare the corresponding event handler function in
methods: {
 // The listen check box selects events with status changes
 onCheckBoxChange(e) {
 // e.target.checked is the latest checked status
 console.log(e.target.checked)
 },
},
1234567
stay EsGoods.vue To declare a custom event in a component:
emits: ['stateChange'],
4. Perfection onCheckBoxChange Function processing logic, call $emit() Function triggers a custom event
methods: {
 // The listen check box selects events with status changes
 onCheckBoxChange(e) {
 // The data sent out is an object, which contains two attributes {id, value}
 this.$emit('stateChange', {
 id: this.id,
 value: e.target.checked,
 })
 },
},
5. stay App.vue Used in the root component EsGoods.vue Component, listen for its stateChange event:
<!-- use goods assembly -->
<es-goods
 v-for="item in goodslist"
 :key="item.id"
 :id="item.id"
 :thumb="item.goods_img"
 :title="item.goods_name"
 :price="item.goods_price"
 :count="item.goods_count"
 :checked="item.goods_state"
 @stateChange="onGoodsStateChange"
></es-goods> 
And in App.vue of methods The following event handling functions are declared in
methods: {
 // Listen for the event that the selected status of a commodity changes
 onGoodsStateChange(e) {
 // 1. Search by id (Note: e is an object, including id and value)
(two properties)
 const findResult = this.goodslist.find(x => x.id === e.id)
 // 2. If the corresponding commodity is found, its selection status will be updated
 if (findResult) {
 findResult.goods_state = e.value
 }
 },
} 

Realize the functions of total, settlement quantity and select all

6.1 dynamic statistics of the total price of the checked goods

Demand analysis: the total price of goods depends on goodslist The change of each item information in the array is suitable for this scenario Computational genus nature .
1. In App.vue The following calculation properties are declared in:
computed: {
 // Total price of the checked item
 amount() {
 // 1. Define the total price of goods
 let a = 0
 // 2. Total commodity price of circular accumulation
 this.goodslist
 .filter(x => x.goods_state)
 .forEach(x => {
 a += x.goods_price * x.goods_count
 })
 // 3. Return the result of accumulation
 return a
 },
},
2. In App.vue Used in EsFooter.vue Component, dynamic binding Total price of the checked item :
<!-- use footer assembly -->
<es-footer :total="0" :amount="amount"
@fullChange="onFullStateChange"></es-footer> 1

6.2 dynamic statistics of the total quantity of checked commodities

Demand analysis:
Total quantity dependency of checked goods goodslist The change in the checked status of goods in this scenario is suitable for using calculation attributes.
1. stay App.vue The following calculation properties are declared in:
computed: {
 // Total quantity of checked items
 total() {
 // 1. Define the total quantity of checked goods
 let t = 0
 // 2. Cyclic accumulation
 this.goodslist
 .filter(x => x.goods_state)
 .forEach(x => (t += x.goods_count))
 // 3. Return calculation results
 return t
 },
},
2. stay App.vue Used in EsFooter.vue Component, dynamic binding Total quantity of checked items :
<!-- use footer assembly -->
<es-footer :total="total" :amount="amount"
@fullChange="onFullStateChange"></es-footer>

6.3 realize the function of selecting all

1. In App.vue In assembly monitoring EsFooter.vue When the selected state of the component changes, it is updated immediately goodslist Select the status of each item in the list:
<!-- use footer assembly -->
<es-footer :total="total" :amount="amount"
@fullChange="onFullStateChange"></es-footer>
2. In onFullStateChange Modify the selected status of each item in the event handling function of:
methods: {
 // Monitor the change of the state of the select all button
 onFullStateChange(isFull) {
 this.goodslist.forEach(x => x.goods_state = isFull)
 },
}

7. Package es counter components

7.1 create and register the EsCounter component

stay src/components/es-counter/ New under directory EsCounter.vue assembly
<template>
 <div>EsCounter assembly</div>
</template> <script>
export default {
 name: 'EsCounter',
}
</script> <style lang="less" scoped></style>
2. In EsGoods.vue Import and register in component EsCounter.vue assembly
// Import counter component
import EsCounter from '../es-counter/EsCounter.vue'
export default {
 name: 'EsGoods',
 components: {
 // Register counter component
 EsCounter,
 }
}
3. In EsGoods.vue of template Used in template structure EsCounter.vue Components:
<div class="bottom">
 <!-- commodity price -->
 <div class="price">¥{{ price.toFixed(2) }}</div>
 <!-- Quantity of goods -->
 <div class="count">
 <!-- use es-counter assembly -->
 <es-counter></es-counter>
 </div>
</div>
7.2 encapsulation es-counter assembly
7.2.0 Packaging requirements
  • 1. Basic layout of rendering components
  • 2. Realize the addition and subtraction of quantity value
  • 3. min. treatment time
  • 4. Use the watch listener to process the results entered in the text box
  • 5. Encapsulate numChange custom event
1 <es-counter :num="count" :min="1" @numChange="getNumber"></es-counter>
7.2.1 Basic layout of rendering components
1. be based on bootstrap Provided Buttons https://v4.bootcss.com/docs/components/buttons/#exampl
es and form-control Basic layout of rendering components:
<template>
 <div class="counter-container">
 <!-- quantity -1 Button -->
 <button type="button" class="btn btn-light btn-sm">-</button>
 <!-- Input box -->
 <input type="number" class="form-control form-control-sm ipt-num" />
 <!-- quantity +1 Button -->
 <button type="button" class="btn btn-light btn-sm">+</button>
 </div>
</template> 
2. Beautify the style of the current component:
counter-container {
 display: flex;
 // Button style
 .btn {
 width: 25px;
 }
 // Style of input box
 .ipt-num {
 width: 34px;
 text-align: center;
 margin: 0 4px;
 }
} 
7.2.2 Realize the rendering and addition and subtraction of numerical values
Train of thought analysis:
  • 1. Addition and subtraction operations need to rely on the data of the EsCounter component
  • 2. The initial data depends on the props passed in by the parent component, and the props initial value passed in by the parent component is transferred to the data to form the internal state of the EsCounter component!

1. In EsCounter.vue The following is declared in the component props :
props: {
 // Quantity value
 num: {
 type: Number,
 default: 0,
 },
},
2. In EsGoods.vue In the component, data is transferred to the through attribute binding EsCounter.vue In components:
<!-- Quantity of goods -->
<div class="count">
 <es-counter :num="count"></es-counter>
</div> 1234
Note: do not directly num adopt v-model Directive bidirectional binding to input Input box because vue regulation: props The value of is read-only! For example, the following is wrong:
<!-- Warning Warning: do not imitate the following operations -->
<input type="number" class="form-control form-control-sm ipt-num"
v-model.number="num" />
3. The right thing to do: put props Initial value of Redeposit reach data Yes, because data The data in is readable and writable! Example generation
The code is as follows:
export default {
 name: 'EsCounter',
 props: {
 // Initial quantity value [read only data]
 num: {
 type: Number,
 default: 0,
 },
 },
 data() {
 return {
 // Internal status value [readable and writable data]
 // The initial value in props can be accessed through this
 number: this.num,
 }
 },
} 
And put data Medium number Bidirectional binding to input Input box:
<input type="number" class="form-control form-control-sm ipt-num"
v-model.number="number" />
4. by -1 and +1 Click event handling function of button binding response:
<button type="button" class="btn btn-light btn-sm"
@click="onSubClick">-</button> <input type="number" class="form-control form-control-sm ipt-num"
v-model.number="number" />
<button type="button" class="btn btn-light btn-sm"
@click="onAddClick">+</button>
And in methods The corresponding event handling functions declared in are as follows
methods: {
 // -Event handling function of 1 button
 onSubClick() {
 this.number -= 1
 },
 // +Event handling function of 1 button
 onAddClick() {
 this.number += 1
 },
},
7.2.3 realization min Processing of minimum value
Demand analysis:
When purchasing goods, the minimum quantity purchased is 1
1. In EsCounter.vue The components are encapsulated as follows: props :
export default {
 name: 'EsCounter',
 props: {
 // Quantity value
 num: {
 type: Number,
 default: 0,
 },
 // minimum value
 min: {
 type: Number,
 // The default value of min attribute is NaN, which means that the minimum value is not limited
 default: NaN,
 },
 },
}
stay -1 In the event handler function of the button min Judge and process the value of:
methods: {
 // -Event handling function of 1 button
 onSubClick() {
 // Judgment condition: the value of Min exists and is less than min after number - 1
 if (!isNaN(this.min) && this.number - 1 < this.min) return
 this.number -= 1
 },
} 
3. stay EsGoods.vue Used in components EsCounter.vue Specify when component min Minimum:
<!-- Quantity of goods -->
<div class="count">
 <!-- The minimum value of the specified quantity is 1 -->
 <es-counter :num="count" :min="1"></es-counter>
</div> 12345
7.2.4 Process the input results of the input box
Train of thought analysis:
  • 1. Convert the entered new value to an integer
  • 2. If the result of conversion is not a number or less than 1, the value of number is forced to be equal to 1
  • 3. If the new value is decimal, assign the conversion result to number

1. For input box v-model Instruction addition .lazy Modifier (when the input box is triggered) change Update on event v
model Data source bound to:
<input type="number" class="form-control form-control-sm ipt-num"
v-model.number.lazy="number" />
2. adopt watch Listener listening number Numerical changes, and implement the code according to the analysis steps
export default {
 name: 'EsCounter',
 watch: {
 // Monitor the change of number value
 number(newVal) {
 // 1. Convert the entered new value to an integer
 const parseResult = parseInt(newVal)
 // 2. If the result of the conversion is not a number or less than 1, the value of number is forced to be equal to
if (isNaN(parseResult) || parseResult < 1) {
 this.number = 1
 return
 }
 // 3. If the new value is decimal, assign the conversion result to number
 if (String(newVal).indexOf('.') !== -1) {
 this.number = parseResult
 return
 }
 console.log(this.number)
 },
 },
} 
7.2.5 Deliver the latest data to users
Demand analysis:
When EsGoods Component use EsCounter Component, expect to be able to listen Quantity of goods At this time, you need to use Self determined
Semantic event The way to put the latest data Passed to the consumer of the component .
stay EsCounter.vue The custom events declared in the component are as follows:
emits : [ 'numChange' ],
2. In EsCounter.vue Component watch Trigger custom event in listener:
watch: {
 number(newVal) {
 // 1. Convert the entered new value to an integer
 const parseResult = parseInt(newVal)
 // 2. If the result of conversion is not a number or less than 1, the value of number is forced to be equal to 1
 if (isNaN(parseResult) || parseResult < 1) {
 this.number = 1
 return
 }
 // 3. If the new value is decimal, assign the conversion result to number
 if (String(newVal).indexOf('.') !== -1) {
 this.number = parseResult
 return
 }
 // Trigger a custom event and pass the latest number value to the user of the component
 this.$emit('numChange', this.number) 
 },
},
3. stay EsGoods.vue Listen in component EsCounter.vue Custom events for components:
<!-- Quantity of goods -->
<div class="count">
 <es-counter :num="count" :min="1" @numChange="getNumber"></escounter>
</div> 1234
And declare the corresponding event handling function as follows:
methods: {
 // Listen for events with changing number
 getNumber(num) {
 console.log(num)
 },
}
7.2.6 Update the number of items in the shopping cart
Train of thought analysis:
  • 1. Get the latest commodity quantity in EsGoods component
  • 2. Declare custom events in EsGoods component
  • 3. Trigger a custom event in EsGoods component and transfer the data object {id, value}
  • 4. Listen to the custom events of EsGoods component in the App root component and update the quantity of corresponding products according to the id
1. In EsGoods.vue Declare custom events in components countChange :
emits : [ 'stateChange' , 'countChange' ],
2. In EsCounter.vue Component numChange In the event handler function, trigger step 1 Declared custom events
<es-counter :num="count" :min="1" @numChange="getNumber"></escounter>
methods: {
 // Listen for events with changing number
 getNumber(num) {
 // Trigger a custom event and transfer the data object {id, value}
 this.$emit('countChange', {
// id of the item
 id: this.id,
 // Latest quantity
 value: num,
 })
 },
}
stay App.vue Used in the root component EsGoods.vue Component, listen for its custom events countChange
<!-- use goods assembly -->
<es-goods
 v-for="item in goodslist"
 :key="item.id"
 :id="item.id"
 :thumb="item.goods_img"
 :title="item.goods_name"
 :price="item.goods_price"
 :count="item.goods_count"
 :checked="item.goods_state"
 @stateChange="onGoodsStateChange"
 @countChange="onGoodsCountChange"
></es-goods>
And in methods Corresponding event handling function declared in:
 // Event to monitor the change of commodity quantity
 onGoodsCountChange(e) {
 // Search by id
 const findResult = this.goodslist.find(x => x.id === e.id)
 // If the corresponding item is found, its quantity will be updated
 if (findResult) {
 findResult.goods_count = e.value
 }
 }
}

Topics: Javascript Vue.js bootstrap