Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

A Comprehensive Guide to Vue.js Framework Fundamentals

Tech May 16 1

What is Vue.js?

Vue.js is a progressive framework for building user interfaces

Vue.js is a data-driven progressive framework

一. Your First Vue Instance

1.1 Steps

1.1.1 Building the User Interface (Frontend Page) — Creating a Vue Instance, Initializing Rendering

  1. Prepare a container, either a div tag or other tag element as the container
  2. Import the package - after importing, the Vue constructor becomes available globally, allowing new Vue()
  3. Create Vue instance new Vue()
  4. Specify configuration options — Rendering Data

① el specifies the mount point, determining which container the Vue instance manages

② data provides the data

These two configuration options determine where and what data to display in the container

<!-- Container  -->
<div id="app">
   {{message}}
</div>

<!-- Local Import -->
<script src="../day01/files/vue.js" ></script>
<!-- CDN Import -->
<!-- <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script> -->
<script>
   const app = new Vue({
       el : '#app',
       data : {
           message : 'Tomorrow will be better!'
       }
   })
</script>

2. Interpolation Expressions {{ expression }}

  • Interpolation is a Vue template syntax
  • Purpose: Use expressions to interpolate and render content to the page
  • Important notes:
  • ① Data used in interpolation must exist in the data option
  • ② Interpolation supports expressions only (expressions must produce a result), not statements - cannot use if statements in interpolation
  • ③ Interpolation cannot be used in tag attributes

2.1 Common Interpolation Errors

  1. When using undefined data 'xyz' not in data, console error:

Property or method "xyz" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property.

The property or method "xyz" was not defined on the instance but was referenced during rendering. Ensure the property is reactive by initializing it in the data option or for class-based components.

  1. When using if statements in interpolation, page won't render, console error:

Error compiling template:

avoid using JavaScript keyword as property name: "if"

Template compilation error:

Avoid using JavaScript keyword as property name: "if"

  1. When using interpolation in tag attributes, console error:

Error compiling template:

title="{{msg}}": Interpolation inside attributes has been removed. Use v-bind or the colon shorthand instead. For example, instead of <div id="{{ val }}">, use <div :id="val">.

Template compilation error:

title="{{msg}}": Interpolation inside attributes has been removed. Use v-bind or the colon shorthand instead.

2.2 Vue's Core Feature: Reactivity — When data changes, the view automatically updates

How to access or modify data in data?

Data in data gets added to the Vue instance: const app = new Vue({})

① Accessing data: "instance.propertyName"

② Modifying data: "instance.propertyName" = "newValue"

Browser Console Data Access and Modification

2.3 Installing Vue DevTools

2.4 Vue Directives

Vue applies different functionality to tags based on various directives

Directives: Special tag attributes with v- prefix

  • v-html: Sets element's innerHTML, parsing tags Syntax: v-html = "expression"
<!-- Container  -->
<div id="app">
    <div v-html="content"></div>
</div>

<!-- Local Import -->
<script src="../day01/files/vue.js" ></script>
<!-- CDN Import -->
<!-- <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script> -->
<script>
    const app = new Vue({
        el : '#app',
        data : {
            content : '<h1>Tomorrow will be better!</h1>',
            quantity : '100'
        }
    })
</script>

1. v-show and v-if
Implementation


   This is a v-show controlled box
   This is a v-if controlled box







   const app = new Vue({
       el : '#app',
       data : {
           content : 'Tomorrow will be better!',
>             quantity : '100',
>             isVisible : true
>         }
>     })
> 

Result: Expression evaluates to false
Result: Expression evaluates to true

2.v-else and v-else-if

  • v-else and v-else-if assist v-if in conditional rendering; v-if must be present
  • v-else has no expression, only v-else-if has an expression


   I will become better 
   
       Grade A
       Grade B
       Grade C
       Grade D
   







   const app = new Vue({
       el : '#app',
       data : {
           // If value is null, defaults to false
           isGood : '',
           score : '75'
       }
   })


Error when using v-else/v-else-if without v-if

Error compiling template:

v-else-if="score > 80" used on element <li> without corresponding v-if.

3 | <div>
4 | <!-- <li v-if="score > 90">Grade A</li> -->
5 | <li v-else-if="score > 80">Grade B</li>
| ^^^^^^^^^^^^^^^^^^^^^^
6 | <li v-else-if="score >70">Grade C</li>
7 | <li v-else="">Grade D</li>

Template compilation error:

v-else-if="score > 80" used on element <li> without corresponding v-if.

When compilation errors occur, check syntax
2.v-on:eventName = "inline handler"
Implementation


   -
   {{quantity}}
   +







   const app = new Vue({
       el : '#app',
       data : {
           quantity : '100',

       }
   })


If event name is missing, error occurs

v-on without argument expects an Object value

v-on without argument expects an Object value

v-on:eventName = "methodName from methods option"
Implementation


   Toggle Visibility
   Visible





   const app = new Vue({
       el : '#app',
       data : {
           isVisible : true,
       },
       methods : {
           toggle(){
               // app.isVisible = !app.isVisible
               this.isVisible = !this.isVisible
           }
       }
   })


Key Points

  • methods cannot directly access data via property names because data and methods are at the same level with local scope
  • How to use data in methods? Data is mounted on the Vue instance, accessed via VueInstance.propertyName, i.e., app.isVisible = !app.isVisible
  • In methods, this refers to the Vue instance — use this.propertyName to access data properties, i.e., this.isVisible = !this.isVisible

Error when accessing undefined data

Error in v-on handler: "ReferenceError: isVisible is not defined"

Error in v-on handler: "ReferenceError: isVisible is not defined"

Passing Arguments with v-on: @click(arg1, arg2, ...)



   
   Vending Machine
   
   Milk $10
   
   Congee $7
   
   Balance: ${{balance}}
   






   const app = new Vue({
       el : '#app',
       data : {
           balance : "100",
       },
       methods : {
           purchace(cost){
               this.balance = this.balance - cost
           }
       }
   })


Error when function receives arguments but doesn't use them

Error in v-on handler: "ReferenceError: cost is not defined"

Error in v-on handler: "ReferenceError: cost is not defined"

3. v-bind

Implementation



   Previous
   
   
       
   
   
   Next






   const app = new Vue({
       el : '#app',
       data : {
           currentIndex : 0,

           images : [
               'img/001.webp',
               'img/002.webp',
               'img/003.webp',
               'img/004.webp',
           ]

       },
   })


4. v-for

Implementation



   
       
           {{item}}
       
   






   const app = new Vue({
       el : '#app',
       data : {
           currentIndex : 0,

           items : [
               'Banana',
               'Pineapple',
               'Apple'
           ]

       },
   })


Example:



   
       
           {{item.title}}
            Delete
       
   






   const app = new Vue({
       el : '#app',
       data : {
           bookList : [
               {id:1,title:'Dream of Red Chamber',author:'Cao Xueqin'},
               {id:2,title:'Journey to the West',author:'Wu Chengen'},
               {id:3,title:'Water Margin',author:'Shi Nai\'an'},
               {id:4,title:'Romance of Three Kingdoms',author:'Luo Guanzhong'},
           ]

       },
       methods:{
           remove(id){
               /* function(item){
                   return item.id !==id
               }
               In JavaScript, arrays have a filter() method that creates a new array with all elements that pass the test
               */
               this.bookList = this.bookList.filter(entry => entry.id !== id)
           }
       }
   })


Key in v-for

Difference with and without key can be observed through CSS styling

Without key: deleting Dream of Red Chamber, style is preserved (li not actually removed)

With key: deleting Dream of Red Chamber, style is also removed

5 . v-model

Variable is defined in data option

Implementation



   
        Username:
        Password:
       Login
       Reset
   





   const app = new Vue({
       el : '#app',
       data : {
           username : '',
           password : '',
       },
       methods:{
           clearForm(){
               this.username='',
               this.password=''
           }
       }
   })


6. Directive Modifiers

Vue directive modifiers essentially wrap event objects

Error when using modifiers incorrectly, correct: v-on:click.prevent="handler" wrong: v-on.prevent:click="handler"

v-on without argument does not support modifiers

v-on without argument does not support modifiers

7.v-bind for class Binding

Array syntax for binding class names requires quotes v-bind:class ="['class1','class2']"

Implementation




   
       v-bind with object syntax for CSS
   
   
       v-bind with array syntax for CSS
   





   const app = new Vue({
       el : '#app',
       data : {
           username : '',
           password : '',
       },
       methods:{
           clearForm(){
               this.username='',
               this.password=''
           }
       }
   })



.yellowBg {
   background-color: yellow;
}
.largeText {
   font-size: 25px;
}
.box {
   width: 700px;
   height: 800px;
}
.boxWrapper{
   width: 500px;
   height: 600px;
}
.boxWrapperColor{
   background-color: aqua;
}



Example: Flash Sale Tab Navigation Highlighting

Example Code




 
 
 
 Document
 
   * {
     margin: 0;
     padding: 0;
   }
   ul {
     display: flex;
     border-bottom: 2px solid #e01222;
     padding: 0 10px;
   }
   li {
     width: 100px;
     height: 50px;
     line-height: 50px;
     list-style: none;
     text-align: center;
   }
   li a {
     display: block;
     text-decoration: none;
     font-weight: bold;
     color: #333333;
   }
   li a.active {
     background-color: #e01222;
     color: #fff;
   }

 



 
   
     
       {{item.label}}
     
   
 
 
 
   const app = new Vue({
     el: '#app',
     data: {
       activeIndex : 0,
       tabs: [
         { id: 1, label: 'Flash Sale' },
         { id: 2, label: 'Daily Deals' },
         { id: 3, label: 'Category Deals' }
       ]

     }
   })
 



8.v-bind for style Binding

When binding styles with v-bind, CSS property names and values must follow JS syntax

  • background-color should be written as 'background-color' or backgroundColor
  • CSS values must be in quotes
  • v-bind:style="{ 'background-color' : 'red' }"

Example Implementation




 
 
 
 Document


 
   
     
       {{percent}}%
     
   
   Set 25%
   Set 50%
   Set 75%
   Set 100%
 
 
 
   const app = new Vue({
     el: '#app',
     data: {
       percent : 0,
     }
   })
 


 .progress {
   height: 25px;
   width: 400px;
   border-radius: 15px;
   background-color: #272425;
   border: 3px solid #272425;
   box-sizing: border-box;
   margin-bottom: 30px;
 }
 .inner {
   width: 50%;
   height: 20px;
   border-radius: 10px;
   text-align: right;
   position: relative;
   background-color: #409eff;
   background-size: 20px 20px;
   box-sizing: border-box;
   transition: all 1s;
 }
 .inner span {
   position: absolute;
   right: -20px;
   bottom: -25px;
 }




9. v-model with Other Form Elements




 
 
 
 Document
 
   textarea {
     display: block;
     width: 240px;
     height: 100px;
     margin: 10px 0;
   }
 



 
   Learning Platform

   Name:
      
     

   Single:
      
     

   
   Gender: 
     Male
     Female
     

   
   City:
     
       Beijing
       Shanghai
       Chengdu
       Nanjing
     
     

   Bio:
      

   Register
 
 
 
   const app = new Vue({
     el: '#app',
     data: {
       fullname:'',
       isSingle:true,
       gender:'1',
       cityId:'1',
       bio:'',

     }
   })
 



10. Computed Properties

  • Based on existing data, derive new properties - if dependent data changes, computed properties recalculate. Example: existing data 1, 2, 5 → computed data 8
  • Computed properties are declared in the computed option, each computed property corresponds to a function
  • Usage is the same as regular properties {{ computedPropertyName }}
  • Computed properties are essentially properties, no parentheses when using (not calling), the name is the function name in computed option
Implementation



 
 
 
 Document
 
   table {
     border: 1px solid #000;
     text-align: center;
     width: 240px;
   }
   th,td {
     border: 1px solid #000;
   }
   h3 {
     position: relative;
   }
 



 
   Gift List
   
     
       Name
       Quantity
     
     
       {{ item.name }}
       {{ item.quantity }} pcs
     
   

   
   Total Items: {{totalItems}} pcs
 
 
 
   const app = new Vue({
     el: '#app',
     data: {
       // Existing data
       gifts: [
         { id: 1, name: 'Basketball', quantity: 1 },
         { id: 2, name: 'Toys', quantity: 2 },
         { id: 3, name: 'Pencils', quantity: 5 },
       ]
     },
     computed:{
       totalItems(){
        let sum = this.gifts.reduce((acc,item) =>acc+item.quantity ,0)
        return sum;
       }
     }
   })
 



Difference Between Computed Properties and Methods

Computed Property Full Syntax

  • get() Gets the computed property value
  • set() Sets the computed property value, affecting the underlying data

Implementation




 
 
 
 Document



 
   First Name:+
   Last Name:={{fullName}}
   Update Name
 
 
 
   const app = new Vue({
     el: '#app',
     data: {
       firstName:'Huang',
       lastName :'fu'
     },
     computed: {
       fullName:{
         get(){
           return this.firstName + this.lastName
         },
         set(value){
           this.firstName = value.slice(0,1)
           this.lastName = value.slice(1)
         }
       }

     },
     methods: {
       updateName(){
         this.fullName = 'Andy Lau'
       }
     }
   })
 



  • When assigning to a computed property, the value passes to set(value) as parameter
  • Example: Assigning to computed property => this.fullName = 'Andy Lau', value in set(value) is 'Andy Lau'

Problem: Assigning to computed property without set(), console error

Computed property "fullName" was assigned to but it has no setter.

Computed property "fullName" was assigned to but it has no setter.

Grade Example:



 
   
   
   
   
   Document
 
 
   
     
       
         
           
             ID
             Subject
             Score
             Action
           
         
         
           
             {{index + 1}}
             {{item.subject}}
             {{item.score}}
             Delete
           
         
         
           
             
               No Data
             
           
         

         
           
             
               Total: {{totalScore}}
               Average: {{averageScore}}
             
           
         
       
     
     
       
         Subject:
         
           
         
       
       
         Score:
         
           
         
       
       
         
         
           Add
         
       
     
   
   

   
     const app = new Vue({
       el: '#app',
       data: {
         grades: [
           { id: 1, subject: 'Chinese', score: 20 },
           { id: 7, subject: 'Math', score: 99 },
           { id: 12, subject: 'English', score: 70 },
         ],
         subject: '',
         score: ''
       },
       methods:{
         remove(id){
           this.grades = this.grades.filter(item => item.id !== id)
         },
         addGrade(){
           if(!this.subject){
             alert("Subject cannot be empty")
             return
           }
           if(typeof this.score !== 'number'){
             alert("Please enter valid score")
             return
           }
            this.grades.unshift({
             id : +new Date(),
             subject : this.subject,
             score : this.score,
           })
           
           this.subject = ''
           this.score = ''

         }
       },
       computed : {
         totalScore(){
           // reduce((accumulative result, item) => result after each calculation, initial value)
           return this.grades.reduce((acc,item) =>acc+item.score,0)
         },
         averageScore(){
           // toFixed keeps two decimal places
           return (this.totalScore / this.grades.length).toFixed(2)
         }
       }
     })
   
 


11.watch Watchers

  • When watched properties or data change, automatically execute related logic operations
Implementation


 
   
   
   
   Document
   
     * {
       margin: 0;
       padding: 0;
       box-sizing: border-box;
       font-size: 18px;
     }
     #app {
       padding: 10px 20px;
     }
     .query {
       margin: 10px 0;
     }
     .box {
       display: flex;
     }
     textarea {
       width: 300px;
       height: 160px;
       font-size: 18px;
       border: 1px solid #dedede;
       outline: none;
       resize: none;
       padding: 10px;
     }
     textarea:hover {
       border: 1px solid #1589f5;
     }
     .transbox {
       width: 300px;
       height: 160px;
       background-color: #f0f0f0;
       padding: 10px;
       border: none;
     }
     .tip-box {
       width: 300px;
       height: 25px;
       line-height: 25px;
       display: flex;
     }
     .tip-box span {
       flex: 1;
       text-align: center;
     }
     .query span {
       font-size: 18px;
     }

     .input-wrap {
       position: relative;
     }
     .input-wrap span {
       position: absolute;
       right: 15px;
       bottom: 15px;
       font-size: 12px;
     }
     .input-wrap i {
       font-size: 20px;
       font-style: normal;
     }
   
 
 
   
     
     
       Translate to:
       
         Italian
         English
         German
       
     

     
     
       
         
         
         ⌨️Document Translation
       
       
         mela
       
     
   
   
   
   
     // API: https://applet-base-api-t.itheima.net/api/translate
     // Method: get
     // Parameters:
     // (1) words: text to translate (required)
     // (2) lang: target language (optional) default - Italian
     // -----------------------------------------------
     
     const app = new Vue({
       el: '#app',
       data: {
         inputText: '',
         obj : {
           // quotes required
           'obj.wordsObj' : ''
         }
       },
       watch : {
         // watching data properties, usually only newValue needed
         inputText(newValue,oldValue){
           console.log(newValue+" new value, old value " + oldValue);
         },
         // watching object properties
         'obj.wordsObj'(newValue,oldValue){
           console.log(newValue+" new value, old value " + oldValue);
         }
       },
       // Details: (1) watch syntax (2) business implementation
     })
   
 



Complete Watcher Syntax

  • When needing to watch entire object properties, using property name(newValue, oldValue) requires multiple watchers
  • Now watching an object to watch all properties, configure two options watch : { objectName : {
  • deep : true
  • handler(newValue, oldValue){ } }
  • }
  • Any property change triggers handler function
  • immediate option, when configured, executes handler once immediately without waiting for change
Example: Shopping Cart


 
   
   
   
   
   
   Shopping Cart
 
 
   
     
     
     
     
       🏠
       /
       Cart
     
     
     
       
         
         
           
             Select
             Image
             Price
             Quantity
             Subtotal
             Action
           
         
         
         
           
             
             
             {{item.price}}
             
               
                  - 
                 {{item.quantity}}
                  + 
               
             
             {{item.price * item.quantity}}
             Delete
           

         
       
       
       
         
         
           
           Select All
         
         
           
           Total &nbsp;&nbsp;:&nbsp;&nbsp;¥&nbsp;{{totalAmount}}
           
           Checkout( {{selectedCount}} )
         
       
     
     
     🛒Cart is empty
   
   
   
     const app = new Vue({
       el: '#app',
       data: {
         // Product list
         productList: [
           {
             id: 1,
             image: 'img/Pitaya.png',
             isChecked: true,
             quantity: 2,
             price: 6,
           },
           {
             id: 2,
             image: 'img/Lychee.png',
             isChecked: false,
             quantity: 7,
             price: 20,
           },
           {
             id: 3,
             image: 'img/Durian.png',
             isChecked: false,
             quantity: 3,
             price: 40,
           },
           {
             id: 4,
             image: 'img/Pear.png',
             isChecked: true,
             quantity: 10,
             price: 3,
           },
           {
             id: 5,
             image: 'img/Cherry.png',
             isChecked: false,
             quantity: 20,
             price: 34,
           },
         ],
       },
       methods : {
         removeItem(id){
           this.productList = this.productList.filter((item) =>item.id !==id )
         },
         increment(id){
           const product = this.productList.find(item => item.id === id)
           product.quantity++
         },
         decrement(id){
           const product = this.productList.find(item => item.id === id)
           product.quantity--
         }
       },
       computed:{
         selectAll:{
           get(){
             // All checkboxes must be checked for select all --every
             return this.productList.every(item => item.isChecked === true)
           },
           set(value){
             this.productList.forEach(element => {
               element.isChecked =value
             });
           },
         },
           ///////////////////////////////////
           selectedCount(){
             return this.productList.reduce((acc,item) => {
               if(item.isChecked){
                 //Selected 
                 return acc + item.quantity 
               }else{
                 return acc
               }
             },0)
           },
           ////////////////////////////////////
           totalAmount(){
             return this.productList.reduce((acc,item) => {
               if(item.isChecked){
                 return acc + item.quantity * item.price
               }else{
                 return acc
               }
             },0)
           }
       },
       watch : {
         productList : {
           deep : true,
           handler (newValue) {
             localStorage.setItem('list',JSON.stringify(newValue))
           }
         }
       }


     })
   
 



二. Lifecycle

Lifecycle Summary: Four phases, eight functions

  • Creation and mounting phases execute once, update phase executes multiple times. During update phase, when data changes, view updates - this is a loop

Four Phases

Eight Functions

  • beforeCreate executes before creation phase, like standing before the creation phase
  • created means data is already reactive
  • Lifecycle hooks are at the same level as data option

Implementation




 
 
 
 Document



 
   {{ title }}
   
     -
     {{ count }}
     +
   
 
 
 
   const app = new Vue({
     el: '#app',
     data: {
       count: 100,
       title: 'Counter'
     },
     // 1. Creation phase (preparing data)
     beforeCreate () {
       console.log('beforeCreate before reactive data is ready', this.count)
     },
     created () {
       console.log('created after reactive data is ready', this.count)
       // this.dataName = fetched data
       // Can start initialization requests
     },

     // 2. Mounting phase (rendering template)
     beforeMount () {
       console.log('beforeMount before template renders', document.querySelector('h3').innerHTML)
     },
     mounted () {
       console.log('mounted after template renders', document.querySelector('h3').innerHTML)
       // Can manipulate DOM now
     },

     // 3. Update phase (modify data → update view)
     beforeUpdate () {
       console.log('beforeUpdate data modified, view not yet updated', document.querySelector('span').innerHTML)
     },
     updated () {
       console.log('updated data modified, view already updated', document.querySelector('span').innerHTML)
     },

     // 4. Unmount phase
     beforeDestroy () {
       console.log('beforeDestroy, before unmount')
       console.log('Clean up external resources, timers, delays...')
     },
     destroyed () {
       console.log('destroyed, after unmount')
     }
   })
 



Console Screenshot

Example



 
   
   
   Document
   
   
   
     .red {
       color: red!important;
     }
     .search {
       width: 300px;
       margin: 20px 0;
     }
     .my-form {
       display: flex;
       margin: 20px 0;
     }
     .my-form input {
       flex: 1;
       margin-right: 20px;
     }
     .table > :not(:first-child) {
       border-top: none;
     }
     .contain {
       display: flex;
       padding: 10px;
     }
     .list-box {
       flex: 1;
       padding: 0 30px;
     }
     .list-box  a {
       text-decoration: none;
     }
     .echarts-box {
       width: 600px;
       height: 400px;
       padding: 30px;
       margin: 0 auto;
       border: 1px solid #ccc;
     }
     tfoot {
       font-weight: bold;
     }
     @media screen and (max-width: 1000px) {
       .contain {
         flex-wrap: wrap;
       }
       .list-box {
         width: 100%;
       }
       .echarts-box {
         margin-top: 30px;
       }
     }
   
 
 
   
     
       
       

         
         
           
           
           Add Bill
         

         
           
             
               ID
               Expense Name
               Amount
               Action
             
           
           
             
               {{ index + 1 }}
               {{ item.name }}
               {{ item.price.toFixed(2) }}
               Delete
             
           
           
             
               Total: {{ totalAmount.toFixed(2) }}
             
           
         
       
       
       
       
     
   
   
   
   
   
     /**
      * API Documentation:
      * https://www.apifox.cn/apidoc/shared-24459455-ebb1-4fdc-8df8-0aff8dc317a8/api-53371058
      * 
      * Features:
      * 1. Basic rendering
      *    (1) Immediately fetch data on created
      *    (2) Store data in reactive data option
      *    (3) Render with v-for
      *    (4) Total calculation => computed property
      * 2. Add feature
      *    (1) Collect form data with v-model
      *    (2) Click handler sends add request
      *    (3) Re-render
      * 3. Delete feature
      *    (1) Click handler passes id
      *    (2) Send delete request based on id
      *    (3) Re-render
      * 4. Pie chart rendering
      *    (1) Initialize chart with echarts.init(dom) in mounted
      *    (2) Update chart with echarts.setOption({ ... })
      */
     const app = new Vue({
       el: '#app',
       data: {
         expenseList: [],
         name: '',
         price: ''
       },
       computed: {
         totalAmount () {
           return this.expenseList.reduce((acc, item) => acc + item.price, 0)
         }
       },
       created () {
         this.fetchExpenses()
       },
       mounted () {
         this.myChart = echarts.init(document.querySelector('#main'))
         this.myChart.setOption({
           // Title
           title: {
             text: 'Expense List',
             left: 'center'
           },
           // Tooltip
           tooltip: {
             trigger: 'item'
           },
           // Legend
           legend: {
             orient: 'vertical',
             left: 'left'
           },
           // Series data
           series: [
             {
               name: 'Expenses',
               type: 'pie',
               radius: '50%',
               data: [
               ],
               emphasis: {
                 itemStyle: {
                   shadowBlur: 10,
                   shadowOffsetX: 0,
                   shadowColor: 'rgba(0, 0, 0, 0.5)'
                 }
               }
             }
           ]
         })
       },

       methods: {
         async fetchExpenses () {
           const res = await axios.get('https://applet-base-api-t.itheima.net/bill', {
             params: {
               creator: 'User'
             }
           })
           this.expenseList = res.data.data

           // Update chart
           this.myChart.setOption({
             series: [
               {
                 data: this.expenseList.map(item => ({ value: item.price, name: item.name}))
               }
             ]
           })
         },
         async addEntry () {
           if (!this.name) {
             alert('Please enter expense name')
             return
           }
           if (typeof this.price !== 'number') {
             alert('Please enter valid amount')
             return
           }

           // Send add request
           const res = await axios.post('https://applet-base-api-t.itheima.net/bill', {
             creator: 'User',
             name: this.name,
             price: this.price
           })
           // Re-render
           this.fetchExpenses()

           this.name = ''
           this.price = ''
         },
         async removeEntry (id) {
           await axios.delete(`https://applet-base-api-t.itheima.net/bill/${id}`)
           this.fetchExpenses()
         }
       }
     })
   
 



三. Scaffolding and Project Structure

1. Vue CLI Scaffolding

2. Standard Directory Structure

Note: When opening project with VSCode, open from root directory

Root Component Overview

 
   
 



// Export is the component options object
// Can include data(special) methods computed watch lifecycle hooks
export default {
 created () {
   console.log('created hook')
 },
 methods: {
   handleClick () {
     alert('Hello')
   }
 }
}



/* Enable less support in style
  1. Add lang="less" to style
  2. Install dependencies less less-loader
     yarn add less less-loader -D (dev dependency)
*/
.App {
 width: 400px;
 height: 400px;
 background-color: pink;
 .box {
   width: 100px;
   height: 100px;
   background-color: skyblue;
 }
}


main.js Overview
// Core function: Import App.vue, render structure based on App.vue into index.html
// 1. Import Vue core library
import Vue from 'vue'

// 2. Import App.vue root component
import App from './App.vue'

// Note: Environment indicator (production/development)
Vue.config.productionTip = false

// 3. Vue instance, provide render method → render structure based on App.vue into index.html
new Vue({
 // el: '#app', same as $mount('#app'), specifies container
 // render: h => h(App),
 render: (createElement) => {
   return createElement(App)
 }
}).$mount('#app')


3. Component-Based Development

4. Three Parts of Components

When style has nested CSS, set style lang to less

  • Need to install dependencies
  • yarn add less less-loader -D ----- -D development dependency
  • Nested CSS
  • <style lang="less">
  • .APP{
  • .box {
  • }
  • }
  • <style>

5.Component Registration

1. Local Component Registration

Tab completion setting: Settings → search trigger on tab → enable

Implementation: Local Registration


 
   
   
   
   
   
   
 



import HeaderComponent from './components/HeaderComponent.vue'
import MainComponent from './components/MainComponent.vue'
import FooterComponent from './components/FooterComponent.vue'

export default {
 name: 'App',
 components: {
   Header: HeaderComponent,
   Main: MainComponent,
   // Same name, can abbreviate
   FooterComponent 
   
 }

}







2. Global Component Registration

  • In main.js, one file can only register one global component
  • Vue.component('componentName', importedComponentObject)

四. Component Communication

1. Three Component Parts Explained

  • Default component styles are global and affect other components. In practice, each component should have independent styles using <style scoped></style>

2. Component's data Option Must Be a Function — This ensures each execution returns a new data object, so multiple instances don't affect each other

.vue component options are objects

3. Component Communication

1. Parent to Child

  1. In parent component, pass value via attribute on child component tag
  2. In child component, receive via props
  3. Use in child component template
Implementation
App.vue root component

 
   
   
 



import Child from './components/Child.vue'

export default {
 name: 'App',

 data() {
   return {
     parentTitle : 'Component Communication: Parent to Child'
   }
 },
 components: {
   Child : Child
 }
}







Child.vue component

 
   
   I'm Child component {{title}}
 



export default {
 name: 'ChildComponent',
 // 2. Receive via props, array values must match bound attribute names 
 props : ['title']
}






2. Child to Parent

Child.vue component

 
   I'm Child component {{title}}
   
   Child to Parent Communication
 



export default {
 name: 'ChildComponent',
 props: ['title'],
 methods: {
   sendToParent() {
     // 2. Use this.$emit() to notify parent  first param is custom event name used in parent binding,
     // second param is new value to pass
     this.$emit("updateTitle",'Tech University')
   }
 }
}






App.vue parent component

 
   
   
 



import Child from './components/Child.vue'

export default {
 name: 'App',

 data() {
   return {
     parentTitle : 'Component Communication: Parent to Child'
   }
 },
 methods: {
   // 4. newValue is the updated value --> Tech University
   handleUpdate(newTitle) {
     this.parentTitle = newTitle
   }
 },
 components: {
   Child : Child
 }
}







Notes
  • vm.$emit("customEventName", "newValue") First param: custom event name used as binding event in parent, second param: data passed from child to parent

3.Props

property attributes

Implementation
App.vue

 
   
   
   
 



import UserInfo from './components/UserInfo.vue'

export default {
 name: 'App',

 data() {
   return {
     username: 'Handsome',
     age: 28,
     isSingle: true,
     car: {
       brand: 'BMW',
     },
     hobbies: ['Basketball', 'Soccer', 'Badminton'],
   }
   },
 components: {
   UserInfo : UserInfo
 }
}







UserInfo.vue

 
   Personal Info Component
   Name: {{username}}
   Age: {{age}}
   Single: {{isSingle}}
   Car: {{car.brand}}
   Hobbies: {{hobbies.join('、')}}
 



export default {
 props:['username','age','isSingle','car','hobbies']
}



.userinfo {
 width: 300px;
 border: 3px solid #000;
 padding: 20px;
}
.userinfo > div {
 margin: 20px 10px;
}


Props Validation
  • Property name : Data type --> Type validation, validates if passed value type matches
App.vue code

 
   
 



import ProgressBar from './components/ProgressBar.vue'
export default {
 data() {
   return {
     progressWidth: 50,
   }
 },
 components: {
   ProgressBar:ProgressBar, 
 },
 methods: {
   receiveWidthUpdate(newValue) {
     this.progressWidth = newValue
   }
 }
}





ProgressBar.vue code

 
   
     {{ width }}%
     Change
   
 



export default {

 // 1. Basic syntax (type validation)
 // props: {
 //   width: Number,
 // },

 // 2. Full syntax (type, default, required, custom validator)
 props: {
   width: {
     type: Number,
     required: true,
     default: 0,
     // validator(val) param val is the passed value
     validator(val) {
       if (val >= 100 || val 
  localStorage -> read on load
export default {
 data() {
   return {
     todos: JSON.parse(localStorage.getItem('todos')) || [
       { id: 1, name: 'Play Basketball' },
       { id: 2, name: 'Watch Movie' },
       { id: 3, name: 'Go Shopping' },
     ],
   }
 },
 components: {
   TodoInput,
   TodoList,
   TodoFooter,
 },
 watch: {
   todos: {
     deep: true,
     handler(newVal) {
       localStorage.setItem('todos', JSON.stringify(newVal))
     },
   },
 },
 methods: {
   handleAdd(taskName) {
     this.todos.unshift({
       id: +new Date(),
       name: taskName,
     })
   },
   handleDelete(id) {
     this.todos = this.todos.filter((item) => item.id !== id)
   },
   clearAll() {
     this.todos = []
   },
 },
}





TodoInput.vue


  
 
   Notes App
   
   Add Task
 



export default {
 data(){
   return {
     taskName:''
   }
 },
 methods:{
   handleAdd(){
     this.$emit('add',this.taskName)
     this.taskName = ''
   }
 }
}






TodoList.vue


 
 
   
     
       
         {{ index + 1 }}.
         {{ item.name }}
         
       
     
   
 



export default {
 props: {
   todos: {
     type: Array,
   },
 },
 methods: {
   handleDelete(id) {
     this.$emit('delete', id)
   },
 },
}





TodoFooter.vue


 
 
   
   Total: {{ todos.length }} 
   
   Clear All
 



export default {
 props: {
   todos: {
     type: Array,
   },
 },
 methods:{
   clearAll(){
     this.$emit('clear')
   }
 }
}





4. Non-Parent-Child Communication

4.1 Event Bus
Implementation
1. Create utils/EventBus.js file
// Event bus, create Vue instance

import Vue from 'vue'

const Bus  =  new Vue()

export default Bus

2. App.vue code

 
   
   
   
 



import ComponentA from './components/ComponentA.vue'
import ComponentB from './components/ComponentB.vue'
import ComponentC from './components/ComponentC.vue'
export default {
 components:{
   ComponentA,
   ComponentB,
   ComponentC
 }
}






3. ComponentB.vue sender code

 
   I'm Component B (Publisher)
   Send Message
 



import Bus from '../utils/EventBus'
export default {
 methods: {
   sendMessage() {
     // vm.$emit() first param is custom event name, must match in receiver
     // vm.$emit() second param is data, use param in receiver
     Bus.$emit('sendMsg', 'Nice weather today, good for travel')
   },
 },
}



.component-b {
 width: 200px;
 height: 200px;
 border: 3px solid #000;
 border-radius: 3px;
 margin: 10px;
}


4. ComponentA.vue receiver code

 
   I'm Component B (Publisher)
   Send Message
 



import Bus from '../utils/EventBus'
export default {
 methods: {
   sendMessage() {
     Bus.$emit('sendMsg', 'Nice weather today, good for travel')
   },
 },
}



.component-b {
 width: 200px;
 height: 200px;
 border: 3px solid #000;
 border-radius: 3px;
 margin: 10px;
}


5. ComponentC.vue receiver code

 
   I'm Component C (Receiver)
   {{message}}  
 



import Bus from '../utils/EventBus'
export default {
 data() {
   return {
     message: '',
   }
 },
 created() {
   Bus.$on('sendMsg', (msg) => {
     this.message = msg
   })
 },
}



.component-c {
 width: 200px;
 height: 200px;
 border: 3px solid #000;
 border-radius: 3px;
 margin: 10px;
}


Event bus is one-to-many: one component publishes, others that receive correctly get the data

4.2 Cross-Level Data Transmission (grandparent-grandchild, etc.)

  • provide: Supply; specify; give --> As data sender
  • inject: Add, inject --> As data receiver
1. app.vue code

 
   I'm App Component
   Change Data
   
   
 



import ChildA from './components/ChildA.vue'
import ChildB from './components/ChildB.vue'
export default {

 provide() {
   return {
     // Simple type is NOT reactive, this.color is just a value
     color: this.color,
     // Complex type IS reactive, this.userData is an object
     userData: this.userData,
   }
 },

 data() {
   return {
     color: 'pink',
     userData: {
       name: 'John',
       age: 18,
     },
   }
 },
 methods: {
   change() {
     this.color = 'red'
     this.userData.name = 'Jane'
   },
 },
 components: {
   ChildA,
   ChildB,
 },
}



.app {
 border: 3px solid #000;
 border-radius: 6px;
 margin: 10px;
}


2. ChildA.vue code

 I'm ChildA Component
   
 



import Grandchild from '../components/Grandchild.vue'
export default {
 components:{
   Grandchild
 }
}



.ChildA {
 border: 3px solid #000;
 border-radius: 6px;
 margin: 10px;
 height: 200px;
}


3. ChildB.vue code

 
   I'm ChildB Component
 



export default {

}



.ChildB {
 border: 3px solid #000;
 border-radius: 6px;
 margin: 10px;
 height: 200px;
}


4. Grandchild.vue is child of ChildA.vue

 
   I'm Grandchild
   {{ color }} -{{ userData.name }} -{{ userData.age }}
 



export default {
 // Receiver, variable names must match provide declarations
 inject: ['color', 'userData'],
}



.grandchild {
 border: 3px solid #000;
 border-radius: 6px;
 margin: 10px;
 height: 100px;
}


5.v-model Deep Dive
6.Form Component Encapsulation (Parent-Child Two-Way Binding)
  • v-model can only implement two-way binding on own data

App.vue code


 
   
   
 



import CitySelect from './components/CitySelect.vue'
export default {
 data() {
   return {
     currentCityId: '102',
   }
 },
 components: {
   CitySelect,
 },
 methods: {
   handleCityChange(val) {
     console.log(val);
     this.currentCityId = val
   }
   
 }
}





CitySelect.vue code


 
   
     Beijing
     Shanghai
     Wuhan
     Guangzhou
     Shenzhen
   
 




export default {
 props: ['selectedCityId'],
 data() {
   return {
     selectedValue: this.selectedCityId,
   }
 },
 methods: {
   onCityChange(e) {
    this.$emit("cityChange",e.target.value) 
   }
 }
}





7.Form Component Encapsulation (Simplified with v-model)

1.When child uses props value (value) to receive from parent and custom event name is input, can use v-model in parent to simplify

App.vue code


 
   
    
   
 



import CitySelect from './components/CitySelect.vue'
export default {
 data() {
   return {
     currentCityId: '102',
   }
 },
 components: {
   CitySelect,
 },
 methods: {
   handleCityChange(val) {
     console.log(val);
     this.currentCityId = val
   }
   
 }
}





CitySelect.vue code


 
   
     Beijing
     Shanghai
     Wuhan
     Guangzhou
     Shenzhen
   
 




export default {
 props: {
   value : String
 },
 methods: {
   onCityChange(e) {
    this.$emit("input",e.target.value) 
   }
 }
}





8.Summary

4.3 sync Modifier

  • sync allows custom prop names

App.vue


 
   Logout
   
 



import Dialog from "./components/Dialog.vue"
export default {
 data() {
   return {
     isDialogVisible : false,
   }
 },
 methods: {
   openDialog() {
     this.isDialogVisible=true
   }
 },
 components: {
   Dialog,
 },
}





Dialog.vue code


 
   
     
       Notice:
       x
     
     
       Are you sure you want to logout?
     
     
       Confirm
       Cancel
     
   
 



export default {
 props: {
   visible : Boolean,
 },
 methods: {
   closeDialog() {
     // Must write update:visible 
     // visible is custom prop name from props
     this.$emit("update:visible",false)
   }
 }

}



.dialog-wrapper {
 width: 300px;
 height: 200px;
 box-shadow: 2px 2px 2px 2px #ccc;
 position: fixed;
 left: 50%;
 top: 50%;
 transform: translate(-50%, -50%);
 padding: 0 10px;
}
.dialog .title {
 display: flex;
 justify-content: space-between;
 align-items: center;
 border-bottom: 2px solid #000;
}
.dialog .content {
 margin-top: 38px;
}
.dialog .title .close {
 width: 20px;
 height: 20px;
 cursor: pointer;
 line-height: 10px;
}
.footer {
 display: flex;
 justify-content: flex-end;
 margin-top: 26px;
}
.footer button {
 width: 80px;
 height: 40px;
}
.footer button:nth-child(1) {
 margin-right: 10px;
 cursor: pointer;
}


4.4 ref and $refs (Accessing DOM and Component Instances)

  • document.querySelector finds all elements, $refs only searches current component
4.4.1 ref and $refs for DOM Access

App.vue


 
   
   
 



import ChartComponent from './components/ChartComponent.vue'
export default {
 components:{
   ChartComponent
 }
}



.chart-box {
 width: 200px;
 height: 100px;
}


ChartComponent.vue


 
 Child Component



import * as echarts from 'echarts'

export default {
 mounted() {
   // Initialize echarts based on prepared dom
   // Get ref corresponding DOM element 
   const chart = echarts.init(this.$refs.myChart)
   // Draw chart
   chart.setOption({
     title: {
       text: 'ECharts Quick Start',
     },
     tooltip: {},
     xAxis: {
       data: ['Shirt', 'Wool Shirt', 'Blouse', 'Pants', 'Heels', 'Socks'],
     },
     yAxis: {},
     series: [
       {
         name: 'Sales',
         type: 'bar',
         data: [5, 20, 36, 10, 10, 20],
       },
     ],
   })
 },
}



.chart-box {
 width: 400px;
 height: 300px;
 border: 3px solid #000;
 border-radius: 6px;
}


4.4.2 ref and $refs for Component Instance Access

App.vue


 
   
   
   Get Data
   Reset
 



import LoginForm from './components/LoginForm.vue'
export default {
 components: {
   LoginForm,
 },
 methods: {
   getData() {
     console.log(this.$refs.LoginForm);
     console.log(this.$refs.LoginForm.getFormValues());
    
   },
   resetData() {
     this.$refs.LoginForm.clearForm()
   }
 }
}





LoginForm.vue code


 
   
     Username: 
   
    
     Password: 
   
 



export default {
 data() {
   return {
     username: 'admin',
     password: '123456',
   }
 },
 methods: {
   getFormValues() {
     return {
       username: this.username,
       password : this.password
     }
   },
   clearForm() {
     this.username = ''
     this.password = ''
     console.log('Form reset successfully');
   },
 }
}



.app {
 border: 2px solid #ccc;
 padding: 10px;
}
.app div{
 margin: 10px 0;
}
.app div button{
 margin-right: 8px;
}


4.5 Vue Async Updates and $nextTick

App.vue code


 
   
     
     Confirm
   
   
     {{ heading }}
     Edit
   
 



export default {
 data() {
   return {
     heading: 'Main Heading',
     showEdit: false,
     editValue: '',
   }
 },
 methods: {
   handleEdit() {
     this.showEdit = true
     // Function body executes after DOM updates
     this.$nextTick( () => {
       this.$refs.inputRef.focus()
     })
   }
 },
}





5.1. Custom Directives

5.1.1 Global Directive Registration

**In main.js, configure global directives

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

Vue.config.productionTip = false

Vue.directive('focus', {
 // inserted triggers when directive's element is inserted into page
 inserted (el) {
   el.focus()
 }
})

new Vue({
 render: h => h(App),
}).$mount('#app')


App.vue use custom directive v-focus


 
   Custom Directives
   
 



export default {

}






5.1.2 Local Custom Directives

Defined within component, can only be used in that component

App.vue code


 
   Custom Directives
   
 



export default {
 // Local directive registration
 directives: {
   // Directive name: directive config
   focus: {
     inserted (el) {
       el.focus()
     }
   }
 }
}






5.1.3 Custom Directive Values

App.vue code


 
   Directive Value Test 1
   Directive Value Test 2
 



export default {
 data () {
   return {
     color1: 'red',
     color2: 'orange'
   }
 },
 directives: {
   color: {
     // inserted provides logic when element is added to page
     inserted (el, binding) {
       // binding.value is the directive value
       el.style.color = binding.value
     },
     // update triggers when directive value changes, provides logic after DOM update
     update (el, binding) {
       console.log('Directive value changed');
       el.style.color = binding.value
     }
   }
 }
}






5.1.4 Custom Loading Directive

App.vue


 
   
   
     
       
         
           {{ item.title }}
           
             {{ item.source }}
             {{ item.time }}
           
         
         
           
         
       
     
   
   
 



import axios from 'axios'

export default {
 data () {
   return {
     newsList: [],
     isLoading: true,
   }
 },
 async created () {
   const res = await axios.get('http://hmajax.itheima.net/api/news')
   setTimeout(() => {
     this.newsList = res.data.data
     this.isLoading = false
   }, 2000)
 },
 directives: {
   loading: {
     inserted (el, binding) {
       binding.value ? el.classList.add('loading') : el.classList.remove('loading')
     },
     update (el, binding) {
       binding.value ? el.classList.add('loading') : el.classList.remove('loading')
     }
   }
 }
}



.loading:before {
 content: '';
 position: absolute;
 left: 0;
 top: 0;
 width: 100%;
 height: 100%;
 background: #fff url('./loading.gif') no-repeat center;
}
.box2 {
 width: 400px;
 height: 400px;
 border: 2px solid #000;
 position: relative;
}
.box {
 width: 800px;
 min-height: 500px;
 border: 3px solid orange;
 border-radius: 5px;
 position: relative;
}
.news {
 display: flex;
 height: 120px;
 width: 600px;
 margin: 0 auto;
 padding: 20px 0;
 cursor: pointer;
}
.news .left {
 flex: 1;
 display: flex;
 flex-direction: column;
 justify-content: space-between;
 padding-right: 10px;
}
.news .left .title {
 font-size: 20px;
}
.news .left .info {
 color: #999999;
}
.news .left .info span {
 margin-right: 20px;
}
.news .right {
 width: 160px;
 height: 120px;
}
.news .right img {
 width: 100%;
 height: 100%;
 object-fit: cover;
}


5.2 Slots

  • Slots allow customized effects on wrapped components
  • Use slots to customize structure of wrapped components

App.vue code


 
   
   
     Are you sure you want to delete?
   

   
     Are you sure you want to exit?
   
 



import ModalDialog from './components/ModalDialog.vue'
export default {
 data () {
   return {

   }
 },
 components: {
   ModalDialog
 }
}



body {
 background-color: #b3b3b3;
}


ModalDialog.vue


 
   
     Notice
     ✖️
   

   
     
      
     
   
   
     Cancel
     Confirm
   
 



export default {
 data () {
   return {

   }
 }
}



* {
 margin: 0;
 padding: 0;
}
.dialog {
 width: 470px;
 height: 230px;
 padding: 0 25px;
 background-color: #ffffff;
 margin: 40px auto;
 border-radius: 5px;
}
.dialog-header {
 height: 70px;
 line-height: 70px;
 font-size: 20px;
 border-bottom: 1px solid #ccc;
 position: relative;
}
.dialog-header .close {
 position: absolute;
 right: 0px;
 top: 0px;
 cursor: pointer;
}
.dialog-content {
 height: 80px;
 font-size: 18px;
 padding: 15px 0;
}
.dialog-footer {
 display: flex;
 justify-content: flex-end;
}
.dialog-footer button {
 width: 65px;
 height: 35px;
 background-color: #ffffff;
 border: 1px solid #e1e3e9;
 cursor: pointer;
 outline: none;
 margin-left: 10px;
 border-radius: 3px;
}
.dialog-footer button:last-child {
 background-color: #007acc;
 color: #fff;
}



5.2.1 Slot Fallback Content

App.vue


 
   
   

   
     Confirm exit?
   
 



import ModalDialog from './components/ModalDialog.vue'
export default {
 data () {
   return {

   }
 },
 components: {
   ModalDialog
 }
}



body {
 background-color: #b3b3b3;
}


ModalDialog.vue


 
   
     Notice
     ✖️
   

   
     
     
     Fallback: Default Content
   
   
     Cancel
     Confirm
   
 



export default {
 data() {
   return {}
 },
}



* {
 margin: 0;
 padding: 0;
}
.dialog {
 width: 470px;
 height: 230px;
 padding: 0 25px;
 background-color: #ffffff;
 margin: 40px auto;
 border-radius: 5px;
}
.dialog-header {
 height: 70px;
 line-height: 70px;
 font-size: 20px;
 border-bottom: 1px solid #ccc;
 position: relative;
}
.dialog-header .close {
 position: absolute;
 right: 0px;
 top: 0px;
 cursor: pointer;
}
.dialog-content {
 height: 80px;
 font-size: 18px;
 padding: 15px 0;
}
.dialog-footer {
 display: flex;
 justify-content: flex-end;
}
.dialog-footer button {
 width: 65px;
 height: 35px;
 background-color: #ffffff;
 border: 1px solid #e1e3e9;
 cursor: pointer;
 outline: none;
 margin-left: 10px;
 border-radius: 3px;
}
.dialog-footer button:last-child {
 background-color: #007acc;
 color: #fff;
}



5.2.2 Named Slots

App.vue code


 
   
     
       Named Slot Header
     Named Slot Content
     
       
         Confirm
         Cancel
       
     
   
 



import ModalDialog from './components/ModalDialog.vue'
export default {
 data () {
   return {

   }
 },
 components: {
   ModalDialog
 }
}



body {
 background-color: #b3b3b3;
}


ModalDialog.vue code


 
   
     Title
   

   
     Content
   
   
     Buttons
   
 



export default {
 data() {
   return {}
 },
}



* {
 margin: 0;
 padding: 0;
}
.dialog {
 width: 470px;
 height: 230px;
 padding: 0 25px;
 background-color: #ffffff;
 margin: 40px auto;
 border-radius: 5px;
}
.dialog-header {
 height: 70px;
 line-height: 70px;
 font-size: 20px;
 border-bottom: 1px solid #ccc;
 position: relative;
}
.dialog-header .close {
 position: absolute;
 right: 0px;
 top: 0px;
 cursor: pointer;
}
.dialog-content {
 height: 80px;
 font-size: 18px;
 padding: 15px 0;
}
.dialog-footer {
 display: flex;
 justify-content: flex-end;
}
.dialog-footer button {
 width: 65px;
 height: 35px;
 background-color: #ffffff;
 border: 1px solid #e1e3e9;
 cursor: pointer;
 outline: none;
 margin-left: 10px;
 border-radius: 3px;
}
.dialog-footer button:last-child {
 background-color: #007acc;
 color: #fff;
}



5.2.3 Scoped Slots

  • row is custom object name
  • Scoped slots can pass data

App.vue code


 
   
   
     
     
       
         Delete
       
     
   
   
   
     
       View
     
   
 



import DataTable from './components/DataTable.vue'
export default {
 data () {
   return {
     list1: [
       { id: 1, name: 'Zhang Xiao', age: 18 },
       { id: 2, name: 'Sun Daming', age: 19 },
       { id: 3, name: 'Liu Dezhong', age: 17 },
     ],
     list2: [
       { id: 1, name: 'Zhao Xiaoyun', age: 18 },
       { id: 2, name: 'Liu Beibei', age: 19 },
       { id: 3, name: 'Jiang Xiaotai', age: 17 },
     ]
   }
 },
 methods: {
   deleteRow (id) {
     this.list1 = this.list1.filter(item => item.id !== id)
   },
   viewRow (row) {
     alert(`Name: ${row.name}; Age: ${row.age}`)
   }
 },
 components: {
   DataTable
 }
}


DataTable.vue code


 
   
     
       ID
       Name
       Age
       Action
     
   
   
     
       {{ index + 1 }}
       {{ item.name }}
       {{ item.age }}
       
         
         

         
         
       
     
   
 



export default {
 props: {
   data: Array
 }
}



.data-table {
 width: 450px;
 text-align: center;
 border: 1px solid #ccc;
 font-size: 24px;
 margin: 30px auto;
}
.data-table thead {
 background-color: #1f74ff;
 color: #fff;
}
.data-table thead th {
 font-weight: normal;
}
.data-table thead tr {
 line-height: 40px;
}
.data-table th,
.data-table td {
 border-bottom: 1px solid #ccc;
 border-right: 1px solid #ccc;
}
.data-table td:last-child {
 border-right: none;
}
.data-table tr:last-child td {
 border-bottom: none;
}
.data-table button {
 width: 65px;
 height: 35px;
 font-size: 18px;
 border: 1px solid #ccc;
 outline: none;
 border-radius: 3px;
 cursor: pointer;
 background-color: #ffffff;
 margin-left: 5px;
}


5.3 Example: Product List

main.js: Focus directive

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

Vue.config.productionTip = false

Vue.directive('focus', {
 inserted (el) {
   el.focus()
 }
})


new Vue({
 render: h => h(App),
}).$mount('#app')

Table.vue code


 
   
     
       
     
   
   
     
       
     
   
 



export default {
 props: {
   data: {
     type: Array,
     required: true
   }
 }
};




.custom-table {
 width: 100%;
 border-spacing: 0;
 img {
   width: 100px;
   height: 100px;
   object-fit: contain;
   vertical-align: middle;
 }
 th {
   background: #f5f5f5;
   border-bottom: 2px solid #069;
 }
 td {
   border-bottom: 1px dashed #ccc;
 }
 td,
 th {
   text-align: center;
   padding: 10px;
   transition: all .5s;
   &.red {
     color: red;
   }
 }
 .none {
   height: 100px;
   line-height: 100px;
   color: #999;
 }
}



TagInput.vue code


 
   
   
     {{ inputValue }}
   
 



export default {
 props: {
   inputValue: String
 },
 data () {
   return {
     isEditing: false
   }
 },
 methods: {
   handleClick () {
     this.isEditing = true
   },
   handleEnter (e) {
     if (e.target.value.trim() === '') return alert('Tag cannot be empty')

     this.$emit('input', e.target.value)
     this.isEditing = false
   }
 }
}



.tag-input {
 cursor: pointer;
 .input {
   appearance: none;
   outline: none;
   border: 1px solid #ccc;
   width: 100px;
   height: 40px;
   box-sizing: border-box;
   padding: 10px;
   color: #666;
   &::placeholder {
     color: #666;
   }
 }
}


App.vue code


 
   
     
       ID
       Name
       Image
       Tag
     

     
       {{ index + 1 }}
       {{ item.name }}
       
         
       
       
         
       
     
   
 



import TagInput from './components/TagInput.vue'
import ProductTable from './components/ProductTable.vue'
export default {
 name: 'TableCase',
 components: {
   TagInput,
   ProductTable
 },
 data () {
   return {
     products: [
       { id: 101, image: 'https://...', name: 'Purple Clay Teapot', tag: 'Tea Set' },
       { id: 102, image: 'https://...', name: 'Waterproof Hiking Shoes', tag: 'Men Shoes' },
       { id: 103, image: 'https://...', name: 'Teddy Bear Vest', tag: 'Kids Clothing' },
       { id: 104, image: 'https://...', name: 'Knit Sweater', tag: 'Kids Clothing' },
     ]
   }
 }
}



.table-case {
 width: 1000px;
 margin: 50px auto;
 img {
   width: 100px;
   height: 100px;
   object-fit: contain;
   vertical-align: middle;
 }
}



  • Install less: npm install less@4 --save-dev
  • Install less-loader: npm install less-loader@7 --save-dev
  • Choose appropriate versions based on build tool version

5.3 Single Page Application

5.4 Vue Router

5.4.1 Vue Router

Version Compatibility (Vue VueRouter Vuex)

  • Vue2 VueRouter3.x Vuex3.x
  • Vue3 VueRouter4.x Vuex4.x

Vue Router can be installed via npm

Documentation: Installation | Vue Router (vuejs.org)

npm install vue-router@4

5.4.2 Configure Global Router in main.js

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

Vue.config.productionTip = false

Vue.use(VueRouter)

const router = new VueRouter()

new Vue({
 render: h => h(App),
 router: router
}).$mount('#app')

5.4.5 Router Configuration

main.js code

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

// Router usage: 5 + 2 steps
// 5 basic steps
// 1. Install v3.6.5
// 2. Import
// 3. Install Vue.use(Vue plugin)
// 4. Create router instance
// 5. Inject into new Vue, establish connection

// 2 core steps
// 1. Create components(views folder), configure rules
// 2. Prepare navigation links, configure router outlet 
import Discover from './views/Discover'
import Mine from './views/Mine'
import Friends from './views/Friends'
import VueRouter from 'vue-router'
Vue.use(VueRouter)

const router = new VueRouter({
 // routes route rules
 // route one rule is an object { path: path, component: component }
 routes: [
   { path: '/discover', component: Discover },
   { path: '/mine', component: Mine },
   { path: '/friends', component: Friends },
 ]
})

Vue.config.productionTip = false

new Vue({
 render: h => h(App),
 router
}).$mount('#app')

App.vue code


 
   
     Discover Music
     My Music
     Friends
   
   
     
   
 



export default {};



body {
 margin: 0;
 padding: 0;
}
.footer_wrap {
 position: relative;
 left: 0;
 top: 0;
 display: flex;
 width: 100%;
 text-align: center;
 background-color: #333;
 color: #ccc;
}
.footer_wrap a {
 flex: 1;
 text-decoration: none;
 padding: 20px 0;
 line-height: 20px;
 background-color: #333;
 color: #ccc;
 border: 1px solid black;
}
.footer_wrap a:hover {
 background-color: #555;
}


Mine.vue code


 
   My Music
   My Music
   My Music
   My Music
 



export default {
 name: 'MyMusic'
}






Discover.vue code


 
   Discover Music
   Discover Music
   Discover Music
   Discover Music
 



export default {
 name: 'DiscoverMusic'
}






Friends.vue code


 
   My Friends
   My Friends
   My Friends
   My Friends
 



export default {
 name: 'MyFriends'
}






5.4.5 Component Directory Organization

5.4.3 Router Module Encapsulation

main.js code

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

Vue.config.productionTip = false

new Vue({
 render: h => h(App),
 router
}).$mount('#app')

router/index.js code

import Discover from '@/views/Discover'
import Mine from '@/views/Mine'
import Friends from '@/views/Friends'
import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

const router = new VueRouter({
 routes: [
   { path: '/discover', component: Discover },
   { path: '/mine', component: Mine },
   { path: '/friends', component: Friends },
 ]
})

export default router

5.5 Declarative Navigation

  • <router-link to="/configured path">

router/index.js code

import Discover from '@/views/Discover'
import Mine from '@/views/Mine'
import Friends from '@/views/Friends'

import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)

const router = new VueRouter({
 routes: [
   { path: '/discover', component: Discover },
   { path: '/mine', component: Mine },
   { path: '/friends', component: Friends },
 ]
})

export default router

main.js code

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

Vue.config.productionTip = false

new Vue({
 render: h => h(App),
 router
}).$mount('#app')

App.vue code


 
   
     Discover Music
     My Music
     Friends
   
   
     
   
 



export default {};



body {
 margin: 0;
 padding: 0;
}
.footer_wrap {
 position: relative;
 left: 0;
 top: 0;
 display: flex;
 width: 100%;
 text-align: center;
 background-color: #333;
 color: #ccc;
}
.footer_wrap a {
 flex: 1;
 text-decoration: none;
 padding: 20px 0;
 line-height: 20px;
 background-color: #333;
 color: #ccc;
 border: 1px solid black;
}
.footer_wrap a.router-link-active {
 background-color: purple;
}
.footer_wrap a:hover {
 background-color: #555;
}


5.5.1 Exact and Fuzzy Matching

5.5.1 Custom Class Names

router/index.js code

import Discover from '@/views/Discover'
import Mine from '@/views/Mine'
import Friends from '@/views/Friends'

import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)

const router = new VueRouter({
 routes: [
   { path: '/discover', component: Discover },
   { path: '/mine', component: Mine },
   { path: '/friends', component: Friends },
 ],
 linkActiveClass: 'active',
 linkExactActiveClass: 'exact-active'
})

export default router

App.vue code


 
   
     Discover Music
     My Music
     Friends
   
   
     
   
 



export default {};



body {
 margin: 0;
 padding: 0;
}
.footer_wrap {
 position: relative;
 left: 0;
 top: 0;
 display: flex;
 width: 100%;
 text-align: center;
 background-color: #333;
 color: #ccc;
}
.footer_wrap a {
 flex: 1;
 text-decoration: none;
 padding: 20px 0;
 line-height: 20px;
 background-color: #333;
 color: #ccc;
 border: 1px solid black;
}
.footer_wrap a.active {
 background-color: purple;
}
.footer_wrap a:hover {
 background-color: #555;
}


5.5.2 Declarative Navigation - Query Parameters

main.js

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

Vue.config.productionTip = false

new Vue({
 render: h => h(App),
 router
}).$mount('#app')

router/index.js code

import HomePage from '@/views/HomePage'
import SearchPage from '@/views/SearchPage'
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)

const router = new VueRouter({
 routes: [
   { path: '/home', component: HomePage },
   { path: '/search', component: SearchPage }
 ]
})

export default router

App.vue code


 
   
     Home
     Search
   

   
 



export default {};



.link {
 height: 50px;
 line-height: 50px;
 background-color: #495150;
 display: flex;
 margin: -8px -8px 0 -8px;
 margin-bottom: 50px;
}
.link a {
 display: block;
 text-decoration: none;
 background-color: #ad2a26;
 width: 100px;
 text-align: center;
 margin-right: 5px;
 color: #fff;
 border-radius: 5px;
}




views/HomePage.vue code


 
   
   
     
     Search
   
   
     Hot Searches:
     Tech Academy
     Frontend Training
     How to Become Expert
   
 



export default {
 name: 'HomePage'
}



.logo-box {
 height: 150px;
 background: url('@/assets/logo.jpeg') no-repeat center;
}
.search-box {
 display: flex;
 justify-content: center;
}
.search-box input {
 width: 400px;
 height: 30px;
 line-height: 30px;
 border: 2px solid #c4c7ce;
 border-radius: 4px 0 0 4px;
 outline: none;
}
.search-box input:focus {
 border: 2px solid #ad2a26;
}
.search-box button {
 width: 100px;
 height: 36px;
 border: none;
 background-color: #ad2a26;
 color: #fff;
 position: relative;
 left: -2px;
 border-radius: 0 4px 4px 0;
}
.hot-link {
 width: 508px;
 height: 60px;
 line-height: 60px;
 margin: 0 auto;
}
.hot-link a {
 margin: 0 5px;
}


views/SearchPage.vue code


 
   Search Keyword: {{ $route.query.keyword }} 
   Results: 
   
     .............
     .............
     .............
     .............
   
 



export default {
 name: 'SearchPage',
 created () {
   console.log(this.$route.query.keyword);
 }
}



.search {
 width: 400px;
 height: 240px;
 padding: 0 20px;
 margin: 0 auto;
 border: 2px solid #c4c7ce;
 border-radius: 5px;
}


5.5.3 Declarative Navigation - Dynamic Route Parameters

router/index.js code

import HomePage from '@/views/HomePage'
import SearchPage from '@/views/SearchPage'
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)

const router = new VueRouter({
 routes: [
   { path: '/home', component: HomePage },
   // Dynamic route /search/:term - :term is parameter, term is custom name
   { path: '/search/:term', component: SearchPage }
 ]
})

export default router

main.js code

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

Vue.config.productionTip = false

new Vue({
 render: h => h(App),
 router
}).$mount('#app')

App.vue


 
   
     Home
     Search
   
   
 



export default {};



.link {
 height: 50px;
 line-height: 50px;
 background-color: #495150;
 display: flex;
 margin: -8px -8px 0 -8px;
 margin-bottom: 50px;
}
.link a {
 display: block;
 text-decoration: none;
 background-color: #ad2a26;
 width: 100px;
 text-align: center;
 margin-right: 5px;
 color: #fff;
 border-radius: 5px;
}




views/HomePage.vue


 
   
   
     
     Search
   
   
     Hot Searches:
     Tech Academy
     Frontend Training
     How to Become Expert
   
 



export default {
 name: 'HomePage'
}



.logo-box {
 height: 150px;
 background: url('@/assets/logo.jpeg') no-repeat center;
}
.search-box {
 display: flex;
 justify-content: center;
}
.search-box input {
 width: 400px;
 height: 30px;
 line-height: 30px;
 border: 2px solid #c4c7ce;
 border-radius: 4px 0 0 4px;
 outline: none;
}
.search-box input:focus {
 border: 2px solid #ad2a26;
}
.search-box button {
 width: 100px;
 height: 36px;
 border: none;
 background-color: #ad2a26;
 color: #fff;
 position: relative;
 left: -2px;
 border-radius: 0 4px 4px 0;
}
.hot-link {
 width: 508px;
 height: 60px;
 line-height: 60px;
 margin: 0 auto;
}
.hot-link a {
 margin: 0 5px;
}


views/SearchPage.vue


 
   Search Term: {{ $route.params.term }} 
   Results: 
   
     .............
     .............
     .............
     .............
   
 



export default {
 name: 'SearchPage',
 created () {
   console.log(this.$route.params.term);
 }
}



.search {
 width: 400px;
 height: 240px;
 padding: 0 20px;
 margin: 0 auto;
 border: 2px solid #c4c7ce;
 border-radius: 5px;
}


  • :parameterName means parameter is required, shows blank if not passed
  • :parameterName? means parameter optional, page renders normally without it

5.5.4 Route Redirect

router/index.js code

import HomePage from '@/views/HomePage'
import SearchPage from '@/views/SearchPage'
import NotFound from '@/views/NotFound'
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)

const router = new VueRouter({
 mode: 'history',
 routes: [
   { path: '/', redirect: '/home' },
   { path: '/home', component: HomePage },
   { name: 'search', path: '/search/:term?', component: SearchPage },
   { path: '*', component: NotFound }
 ]
})

export default router

5.5.5 Basic Programmatic Navigation

router/index.js code

import HomePage from '@/views/HomePage'
import SearchPage from '@/views/SearchPage'
import NotFound from '@/views/NotFound'
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)

const router = new VueRouter({
 mode: 'history',
 routes: [
   { path: '/', redirect: '/home' },
   { path: '/home', component: HomePage },
   { name: 'search', path: '/search/:term?', component: SearchPage },
   { path: '*', component: NotFound }
 ]
})

export default router

views/HomePage.vue code


 
   
   
     
     Search
   
   
     Hot Searches:
     Tech Academy
     Frontend Training
     How to Become Expert
   
 



export default {
 name: 'HomePage',
 methods: {
   goSearch () {
     this.$router.push({
       name: 'search'
     })
   }
 }
}



.logo-box {
 height: 150px;
 background: url('@/assets/logo.jpeg') no-repeat center;
}
.search-box {
 display: flex;
 justify-content: center;
}
.search-box input {
 width: 400px;
 height: 30px;
 line-height: 30px;
 border: 2px solid #c4c7ce;
 border-radius: 4px 0 0 4px;
 outline: none;
}
.search-box input:focus {
 border: 2px solid #ad2a26;
}
.search-box button {
 width: 100px;
 height: 36px;
 border: none;
 background-color: #ad2a26;
 color: #fff;
 position: relative;
 left: -2px;
 border-radius: 0 4px 4px 0;
}
.hot-link {
 width: 508px;
 height: 60px;
 line-height: 60px;
 margin: 0 auto;
}
.hot-link a {
 margin: 0 5px;
}


5.5.6 Programmatic Navigation - Route Params

router/index.js code

import HomePage from '@/views/HomePage'
import SearchPage from '@/views/SearchPage'
import NotFound from '@/views/NotFound'
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)

const router = new VueRouter({
 mode: 'history',
 routes: [
   { path: '/', redirect: '/home' },
   { path: '/home', component: HomePage },
   { name: 'search', path: '/search/:term?', component: SearchPage },
   { path: '*', component: NotFound }
 ]
})

export default router

main.js

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

Vue.config.productionTip = false

new Vue({
 render: h => h(App),
 router
}).$mount('#app')

App.vue


 
   
     Home
     Search
   
   
 



export default {};



.link {
 height: 50px;
 line-height: 50px;
 background-color: #495150;
 display: flex;
 margin: -8px -8px 0 -8px;
 margin-bottom: 50px;
}
.link a {
 display: block;
 text-decoration: none;
 background-color: #ad2a26;
 width: 100px;
 text-align: center;
 margin-right: 5px;
 color: #fff;
 border-radius: 5px;
}




views/HomePage.vue


 
   
   
     
     Search
   
   
     Hot Searches:
     Tech Academy
     Frontend Training
     How to Become Expert
   
 



export default {
 name: 'HomePage',
 data () {
   return {
     searchTerm: ''
   }
 },
 methods: {
   goSearch () {
     this.$router.push({
       name: 'search',
       params: {
         term: this.searchTerm
       }
     })
   }
 }
}



.logo-box {
 height: 150px;
 background: url('@/assets/logo.jpeg') no-repeat center;
}
.search-box {
 display: flex;
 justify-content: center;
}
.search-box input {
 width: 400px;
 height: 30px;
 line-height: 30px;
 border: 2px solid #c4c7ce;
 border-radius: 4px 0 0 4px;
 outline: none;
}
.search-box input:focus {
 border: 2px solid #ad2a26;
}
.search-box button {
 width: 100px;
 height: 36px;
 border: none;
 background-color: #ad2a26;
 color: #fff;
 position: relative;
 left: -2px;
 border-radius: 0 4px 4px 0;
}
.hot-link {
 width: 508px;
 height: 60px;
 line-height: 60px;
 margin: 0 auto;
}
.hot-link a {
 margin: 0 5px;
}


views/SearchPage.vue


 
   Search Term: {{ $route.params.term }} 
   Results: 
   
     .............
     .............
     .............
     .............
   
 



export default {
 name: 'SearchPage',
 created () {
 }
}




.search {
 width: 400px;
 height: 240px;
 padding: 0 20px;
 margin: 0 auto;
 border: 2px solid #c4c7ce;
 border-radius: 5px;
}


views/NotFound.vue


 
   404 Not Found
 



export default {

}






5.5.7 Component Caching

router/index.js code

import Vue from 'vue'
import VueRouter from "vue-router";
import Layout from '@/views/Layout'
import ArticleList from '@/views/ArticleList'
import Favorites from '@/views/Favorites'
import Likes from '@/views/Likes'
import Profile from '@/views/Profile'
import ArticleDetail from '@/views/ArticleDetail'
Vue.use(VueRouter)

const router = new VueRouter({
 routes: [
   { 
     path: '/',
     component: Layout,
     redirect: '/article',
     children: [
       {
         path: '/article',
         component: ArticleList
       },
       {
         path: '/favorites',
         component: Favorites
       },
       {
         path: '/likes',
         component: Likes,
       },
       {
         path: '/profile',
         component: Profile
       }
     ]
   },
   {
     path: '/detail/:id',
     component: ArticleDetail
   }
 ]
})

export default router

main.js code

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

Vue.config.productionTip = false

new Vue({
 render: h => h(App),
 router
}).$mount('#app')

App.vue


 
   
     
   
 



export default {
 name: 'h5-wrapper',
 data () {
   return {
     keepArr: ['LayoutPage']
   }
 }
}



body{
 margin: 0;
 padding: 0;
}


.h5-wrapper {
 .content {
   margin-bottom: 51px;
 }
 .tabbar {
   position: fixed;
   left: 0;
   bottom: 0;
   width: 100%;
   height: 50px;
   line-height: 50px;
   text-align: center;
   display: flex;
   background: #fff;
   border-top: 1px solid #e4e4e4;
   a {
     flex: 1;
     text-decoration: none;
     font-size: 14px;
     color: #333;
     &.router-link-active {
       color: #fa0;
     }
   }
 }
}


ArticleList.vue


 
   
     
       
       
         {{ item.title }}
         {{ item.authorName }} | {{ item.date }}
       
     
     
       {{ item.summary }}
     
     Likes {{ item.likes }} | Views {{ item.views }}
   
 



import axios from 'axios'
export default {
 name: 'ArticleListPage',
 data () {
   return {
     articles: []
   }
 },
 async created () {
   const res = await axios.get('https://mock.boxuegu.com/mock/3083/articles')
   this.articles = res.data.result.rows
 }
}



.article-list {
 background: #f5f5f5;
}
.article-item {
 margin-bottom: 10px;
 background: #fff;
 padding: 10px 15px;
 .head {
   display: flex;
   img {
     width: 40px;
     height: 40px;
     border-radius: 50%;
     overflow: hidden;
   }
   .con {
     flex: 1;
     overflow: hidden;
     padding-left: 15px;
     p {
       margin: 0;
       line-height: 1.5;
       &.title {
         text-overflow: ellipsis;
         overflow: hidden;
         width: 100%;
         white-space: nowrap;
       }
       &.other {
         font-size: 10px;
         color: #999;
       }
     }
   }
 }
 .body {
   font-size: 14px;
   color: #666;
   line-height: 1.6;
   margin-top: 10px;
   overflow: hidden;
   text-overflow: ellipsis;
   display: -webkit-box;
   -webkit-line-clamp: 2;
   -webkit-box-orient: vertical;
 }
 .foot {
   font-size: 12px;
   color: #999;
   margin-top: 10px;
 }
}


ArticleDetail.vue


 
    &lt; Article Detail
   
     {{ article.title }}
     {{ article.date }} | {{ article.views }} Views | {{ article.likes }} Likes
     
        
       {{ article.authorName }} 
     
   
     
     {{ article.content }}
   
 



import axios from 'axios'
export default {
 name: 'ArticleDetailPage',
 data () {
   return {
     article: {}
   }
 },
 async created () {
   const id = this.$route.params.id
   const { data } = await axios.get(`https://mock.boxuegu.com/mock/3083/articles/${id}`)
   this.article = data.result
 }
};



.article-detail {
 .nav {
   height: 44px;
   border-bottom: 1px solid #e4e4e4;
   line-height: 44px;
   text-align: center;
   .back {
     font-size: 18px;
     color: #666;
     position: absolute;
     left: 10px;
     top: 0;
     transform: scale(1, 1.5);
   }
 }
 .header {
    padding: 0 15px;
    p {
      color: #999;
      font-size: 12px;
      display: flex;
      align-items: center;
    }
    img {
      width: 40px;
      height: 40px;
      border-radius: 50%;
      overflow: hidden;
    }
 }
 .body {
   padding: 0 15px;
 }
}


Layout.vue


 
   
     
   
   
     Articles
     Favorites
     Likes
     Profile
   
 



export default {
 name: 'LayoutPage',
 created () {
   console.log('created component loaded');
 },
 mounted () {
   console.log('mounted DOM rendered');
 },
 destroyed () {
   console.log('destroyed component destroyed');
 },
 activated () {
   alert('Welcome back')
   console.log('activated component activated');
 },
 deactivated () {
   console.log('deactivated component deactivated');
 }
}



body{
 margin: 0;
 padding: 0;
}


.h5-wrapper {
 .content {
   margin-bottom: 51px;
 }
 .tabbar {
   position: fixed;
   left: 0;
   bottom: 0;
   width: 100%;
   height: 50px;
   line-height: 50px;
   text-align: center;
   display: flex;
   background: #fff;
   border-top: 1px solid #e4e4e4;
   a {
     flex: 1;
     text-decoration: none;
     font-size: 14px;
     color: #333;
   }
   a.router-link-active {
     color: orange;
   }
 }
}


5.6 Creating Projects with Vue CLI

  1. Create new Vue project

vue create project-name

  1. Select Manually select features
  2. Select features
  3. Choose Vue version
  4. Router mode
  • Use history mode for router?
  1. Choose CSS pre-processor
  2. Choose ESLint config
  3. Pick lint features
  4. Where to place config
  5. Save preset

5.7 ESLint Code Standards

Auto-fix Configuration

In settings

  1. Vuex

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.