Vue2:组件基础(上)

Vue2:组件基础(上)

Date: July 29, 2023
Sum: 生命周期、Vue-cli、组件的使用、小黑记账清单、小兔鲜首页


生命周期:

生命周期介绍

思考:

什么时候可以发送初始化渲染请求?(越早越好)

什么时候可以开始操作dom?(至少dom得渲染出来)

Vue生命周期:就是一个Vue实例从创建 到 销毁 的整个过程。

生命周期四个阶段:① 创建 ② 挂载 ③ 更新 ④ 销毁

1.创建阶段:创建响应式数据

将普通数据转换成响应式数据

2.挂载阶段:渲染模板

结合数据渲染模版

3.更新阶段:修改数据,更新视图

数据修改与更新视图循环

4.销毁阶段:销毁Vue实例

Untitled

注意:创建和挂载阶段只有一次,但是更新阶段会循环多次

理解:了解了Vue的生命周期后,可以理解在何时去调用相应的代码

比如在创建阶段结束之后,我们才能发送初始化的渲染请求。在挂载阶段结束之后,才能操作dom



生命周期钩子

Vue生命周期过程中,会自动运行一些函数,被称为【生命周期钩子】→ 让开发者可以在【特定阶段】运行自己的代码

Untitled

注意:挂载阶段在mounted完成后,模版已经渲染完成了

注意:卸载阶段在你关闭浏览器时调用

案例

Untitled

  • Code:

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
    </head>
    <body>
    
      <div id="app">
        <h3>{
         
         { title }}</h3>
        <div>
          <button @click="count--">-</button>
          <span>{
         
         { count }}</span>
          <button @click="count++">+</button>
        </div>
      </div>
      <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
      <script>
        const app = new Vue({
          el: '#app',
          data: {
            count: 100,
            title: '计数器'
          },
          // 1. 创建阶段(准备数据)
          beforeCreate () {
            console.log('beforeCreate 响应式数据准备好之前', this.count)
          },
          created () {
            console.log('created 响应式数据准备好之后', this.count)
            // this.数据名 = 请求回来的数据
            // 可以开始发送初始化渲染的请求了
          },
    
          // 2. 挂载阶段(渲染模板)
          beforeMount () {
            console.log('beforeMount 模板渲染之前', document.querySelector('h3').innerHTML)
          },
          mounted () {
            console.log('mounted 模板渲染之后', document.querySelector('h3').innerHTML)
            // 可以开始操作dom了
          },
    
          // 3. 更新阶段(修改数据 → 更新视图)
          beforeUpdate () {
            console.log('beforeUpdate 数据修改了,视图还没更新', document.querySelector('span').innerHTML)
          },
          updated () {
            console.log('updated 数据修改了,视图已经更新', document.querySelector('span').innerHTML)
          },
    
          // 4. 卸载阶段
          beforeDestroy () {
            console.log('beforeDestroy, 卸载前')
            console.log('清除掉一些Vue以外的资源占用,定时器,延时器...')
          },
          destroyed () {
            console.log('destroyed,卸载后')
          }
        })
      </script>
    </body>
    </html>
    

注:在生命周期中销毁后,以上的组件即使去按+或-也没有用了,因为以上DOM已于Vue无关了。

补充:在浏览器console中输入

app.$destory()

即可触发 beforeDestory 和 destroyed



生命周期案例

案例1:

需求:一进页面就发送请求,去获取对应的数据,来渲染出来

思考:在created钩子中发送初始化渲染请求, 采用axios方式请求数据

Untitled

  • Code:

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
      <style>
        * {
          margin: 0;
          padding: 0;
          list-style: none;
        }
        .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;
        }
      </style>
    </head>
    <body>
    
      <div id="app">
        <ul>
          <li v-for="(item, index) in list" :key="item.id" class="news">
            <div class="left">
              <div class="title">{
         
         { item.title }}</div>
              <div class="info">
                <span>{
         
         { item.source }}</span>
                <span>{
         
         { item.time }}</span>
              </div>
            </div>
            <div class="right">
              <img :src="item.img" alt="">
            </div>
          </li>
        </ul>
      </div>
      <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
      <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
      <script>
        // 接口地址:http://hmajax.itheima.net/api/news
        // 请求方式:get
        const app = new Vue({
          el: '#app',
          data: {
            list: []
          },
          async created () {
            // 1. 发送请求获取数据
            const res = await axios.get('http://hmajax.itheima.net/api/news')
            // 2. 更新到 list 中,用于页面渲染 v-for
            this.list = res.data.data
          }
        })
      </script>
    </body>
    </html>
    

案例2:

需求:一进页面就获取焦点

思考:在mounted钩子上获取焦点

Untitled

注意:挂载阶段在mounted完成后,模版已经渲染完成了

  • Code:

    <!DOCTYPE html>
    <html lang="zh-CN">
    
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>示例-获取焦点</title>
      <!-- 初始化样式 -->
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/reset.min.css">
      <!-- 核心样式 -->
      <style>
        html,
        body {
          height: 100%;
        }
        .search-container {
          position: absolute;
          top: 30%;
          left: 50%;
          transform: translate(-50%, -50%);
          text-align: center;
        }
        .search-container .search-box {
          display: flex;
        }
        .search-container img {
          margin-bottom: 30px;
        }
        .search-container .search-box input {
          width: 512px;
          height: 16px;
          padding: 12px 16px;
          font-size: 16px;
          margin: 0;
          vertical-align: top;
          outline: 0;
          box-shadow: none;
          border-radius: 10px 0 0 10px;
          border: 2px solid #c4c7ce;
          background: #fff;
          color: #222;
          overflow: hidden;
          box-sizing: content-box;
          -webkit-tap-highlight-color: transparent;
        }
        .search-container .search-box button {
          cursor: pointer;
          width: 112px;
          height: 44px;
          line-height: 41px;
          line-height: 42px;
          background-color: #ad2a27;
          border-radius: 0 10px 10px 0;
          font-size: 17px;
          box-shadow: none;
          font-weight: 400;
          border: 0;
          outline: 0;
          letter-spacing: normal;
          color: white;
        }
        body {
          background: no-repeat center /cover;
          background-color: #edf0f5;
        }
      </style>
    </head>
    
    <body>
    <div class="container" id="app">
      <div class="search-container">
        <img src="https://www.itheima.com/images/logo.png" alt="">
        <div class="search-box">
          <input type="text" v-model="words" id="inp">
          <button>搜索一下</button>
        </div>
      </div>
    </div>
    
    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    <script>
      const app = new Vue({
        el: '#app',
        data: {
          words: ''
        },
        // 核心思路:
        // 1. 等input框渲染出来 mounted 钩子
        // 2. 让input框获取焦点 inp.focus()
        mounted () {
          document.querySelector('#inp').focus()
        }
      })
    </script>
    
    </body>
    
    </html>
    



综合案例-小黑记账清单

1-需求图示

Untitled

2-需求分析

1.基本渲染

2.添加功能

3.删除功能

4.饼图渲染

3-思路分析

1.基本渲染

  • 立刻发送请求获取数据 created
  • 拿到数据,存到data的响应式数据中
  • 结合数据,进行渲染 v-for
  • 消费统计 —> 计算属性

2.添加功能

  • 收集表单数据 v-model,使用指令修饰符处理数据
  • 给添加按钮注册点击事件,对输入的内容做非空判断,发送请求
  • 请求成功后,对文本框内容进行清空
  • 重新渲染列表

3.删除功能

  • 注册点击事件,获取当前行的id
  • 根据id发送删除请求
  • 需要重新渲染

4.饼图渲染

  • 初始化一个饼图 echarts.init(dom) mounted钩子中渲染
  • 根据数据试试更新饼图 echarts.setOptions({…})

Untitled

实现基本渲染功能

  • Code: 实现基本渲染

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
        <!-- CSS only -->
        <link
          rel="stylesheet"
          href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
        />
        <style>
          .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;
            }
          }
        </style>
      </head>
      <body>
        <div id="app">
          <div class="contain">
            <!-- 左侧列表 -->
            <div class="list-box">
    
              <!-- 添加资产 -->
              <form class="my-form">
                <input type="text" class="form-control" placeholder="消费名称" />
                <input type="text" class="form-control" placeholder="消费价格" />
                <button type="button" class="btn btn-primary">添加账单</button>
              </form>
    
              <table class="table table-hover">
                <thead>
                  <tr>
                    <th>编号</th>
                    <th>消费名称</th>
                    <th>消费价格</th>
                    <th>操作</th>
                  </tr>
                </thead>
                <tbody>
                  <tr v-for="(item, index) in list" :key="item.id">
                    <td>{
         
         { index + 1 }}</td>
                    <td>{
         
         { item.name }}</td>
                    <td :class="{ red: item.price > 500}">{
         
         { item.price.toFixed(2) }}</td>
                    <td><a href="javascript:;">删除</a></td>
                  </tr>
                </tbody>
                <tfoot>
                  <tr>
                    <td colspan="4">消费总计: {
         
         { totalPrice.toFixed(2) }}</td>
                  </tr>
                </tfoot>
              </table>
            </div>
            
            <!-- 右侧图表 -->
            <div class="echarts-box" id="main"></div>
          </div>
        </div>
        <script src="../echarts.min.js"></script>
        <script src="../vue.js"></script>
        <script src="../axios.js"></script>
        <script>
          /**
           * 接口文档地址:
           * https://www.apifox.cn/apidoc/shared-24459455-ebb1-4fdc-8df8-0aff8dc317a8/api-53371058
           * 
           * 功能需求:
           * 1. 基本渲染
           * 2. 添加功能
           * 3. 删除功能
           * 4. 饼图渲染
           */
          const app = new Vue({
            el: '#app',
            data: {
              list: [],
            },
            computed: {
              totalPrice() {
                return this.list.reduce((sum, item) => sum += item.price, 0)
              }
            },
            async created() {
              // const res = await axios({
              //   url: 'https://applet-base-api-t.itheima.net/bill',
              //   method: 'get',
              //   params: {
              //     creator: 'Nathan',
              //   }
              // })
              const res = await axios.get('https://applet-base-api-t.itheima.net/bill', {
                params: {
                  creator: 'Nathan'
                }
              })
              this.list = res.data.data
            }
          })
        </script> 
      </body>
    </html>
    

实现添加功能

Untitled

重点:提交完数据后需要重新axios请求数据渲染页面,以下是建议封装一下axios请求

注意一下get和post的两种请求格式:

get后面的{}中需要加 params

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

post后面的{}中不需要加 params


async add() {
  if(!this.name) {
    alert('请输入消费名称')
    return
  }
  if(typeof this.price !== 'number') {
    alert('请输入正确的价格')
  }
  const res = await axios.post('https://applet-base-api-t.itheima.net/bill', {
    creator: 'Nathan',
    name: this.name,
    price: this.price,
  })
  this.getList()
  this.name = ''
  this.price = ''
}
  • Code:

    
    

实现删除功能

  • Code:

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
        <!-- CSS only -->
        <link
          rel="stylesheet"
          href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
        />
        <style>
          .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;
            }
          }
        </style>
      </head>
      <body>
        <div id="app">
          <div class="contain">
            <!-- 左侧列表 -->
            <div class="list-box">
    
              <!-- 添加资产 -->
              <form class="my-form">
                <input v-model.trim="name" type="text" class="form-control" placeholder="消费名称" />
                <input v-model.number="price" type="text" class="form-control" placeholder="消费价格" />
                <button @click="add" type="button" class="btn btn-primary">添加账单</button>
              </form>
    
              <table class="table table-hover">
                <thead>
                  <tr>
                    <th>编号</th>
                    <th>消费名称</th>
                    <th>消费价格</th>
                    <th>操作</th>
                  </tr>
                </thead>
                <tbody>
                  <tr v-for="(item, index) in list" :key="item.id">
                    <td>{
         
         { index + 1 }}</td>
                    <td>{
         
         { item.name }}</td>
                    <td :class="{ red: item.price > 500 }">{
         
         { item.price.toFixed(2) }}</td>
                    <td><a @click="del(item.id)" href="javascript:;">删除</a></td>
                  </tr>
                </tbody>
                <tfoot>
                  <tr>
                    <td colspan="4">消费总计: {
         
         { totalPrice.toFixed(2) }}</td>
                  </tr>
                </tfoot>
              </table>
            </div>
            
            <!-- 右侧图表 -->
            <div class="echarts-box" id="main"></div>
          </div>
        </div>
        <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/echarts.min.js"></script>
        <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
        <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
        <script>
          /**
           * 接口文档地址:
           * https://www.apifox.cn/apidoc/shared-24459455-ebb1-4fdc-8df8-0aff8dc317a8/api-53371058
           * 
           * 功能需求:
           * 1. 基本渲染
           *    (1) 立刻发送请求获取数据 created
           *    (2) 拿到数据,存到data的响应式数据中
           *    (3) 结合数据,进行渲染 v-for
           *    (4) 消费统计 => 计算属性
           * 2. 添加功能
           *    (1) 收集表单数据 v-model
           *    (2) 给添加按钮注册点击事件,发送添加请求
           *    (3) 需要重新渲染
           * 3. 删除功能
           *    (1) 注册点击事件,传参传 id
           *    (2) 根据 id 发送删除请求
           *    (3) 需要重新渲染
           * 4. 饼图渲染
           */
          const app = new Vue({
            el: '#app',
            data: {
              list: [],
              name: '',
              price: ''
            },
            computed: {
              totalPrice () {
                return this.list.reduce((sum, item) => sum + item.price, 0)
              }
            },
            created () {
              // const res = await axios.get('https://applet-base-api-t.itheima.net/bill', {
              //   params: {
              //     creator: '小黑'
              //   }
              // })
              // this.list = res.data.data
    
              this.getList()
            },
            methods: {
              async getList () {
                const res = await axios.get('https://applet-base-api-t.itheima.net/bill', {
                  params: {
                    creator: '小黑'
                  }
                })
                this.list = res.data.data
              },
              async add () {
                if (!this.name) {
                  alert('请输入消费名称')
                  return
                }
                if (typeof this.price !== 'number') {
                  alert('请输入正确的消费价格')
                  return
                }
    
                // 发送添加请求
                const res = await axios.post('https://applet-base-api-t.itheima.net/bill', {
                  creator: '小黑',
                  name: this.name,
                  price: this.price
                })
                // 重新渲染一次
                this.getList()
    
                this.name = ''
                this.price = ''
              },
              async del (id) {
                // 根据 id 发送删除请求
                const res = await axios.delete(`https://applet-base-api-t.itheima.net/bill/${id}`)
                // 重新渲染
                this.getList()
              }
            }
          })
        </script>
      </body>
    </html>
    

饼图渲染功能

关键:

1-在mounted处利用this把mychart绑定到Vue对象实例上

mounted() {
  // 利用 this 暂时存取一下myChart
  this.myChart = echarts.init(document.querySelector('#main'))
  // 指定图表的配置项和数据
  var option = {
    title: {
      text: 'Referer of a Website',
      subtext: 'Fake Data',
      left: 'center'
    },
    tooltip: {
      trigger: 'item'
    },
    legend: {
      orient: 'vertical',
      left: 'left'
    },
    series: [
      {
        name: 'Access From',
        type: 'pie',
        radius: '50%',
        data: [
          { value: 1048, name: 'Search Engine' },
          { value: 735, name: 'Direct' },
          { value: 580, name: 'Email' },
          { value: 484, name: 'Union Ads' },
          { value: 300, name: 'Video Ads' }
        ],
        emphasis: {
          itemStyle: {
            shadowBlur: 10,
            shadowOffsetX: 0,
            shadowColor: 'rgba(0, 0, 0, 0.5)'
          }
        }
      }
    ]
  };
  // 使用刚指定的配置项和数据显示图表。
  this.myChart.setOption(option);
}

2-引入echarts后,我们需要对getList()下的内容进行重新渲染,加上myChart.setOption,其关键点在于需要使用this来进行上下绑定。

this.myChart.setOption({
  series: [
    {
      // data: [
      //   { value: 1048, name: 'Search Engine' },
      //   { value: 735, name: 'Direct' },
      // ],
      data: this.list.map(item => ({ value: item.price, name: item.name }))
    }
  ]
})
  • Code:

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
        <!-- CSS only -->
        <link
          rel="stylesheet"
          href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
        />
        <style>
          .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;
            }
          }
        </style>
      </head>
      <body>
        <div id="app">
          <div class="contain">
            <!-- 左侧列表 -->
            <div class="list-box">
    
              <!-- 添加资产 -->
              <form class="my-form">
                <input v-model.trim="name" type="text" class="form-control" placeholder="消费名称" />
                <input v-model.number="price" type="text" class="form-control" placeholder="消费价格" />
                <button @click="add" type="button" class="btn btn-primary">添加账单</button>
              </form>
    
              <table class="table table-hover">
                <thead>
                  <tr>
                    <th>编号</th>
                    <th>消费名称</th>
                    <th>消费价格</th>
                    <th>操作</th>
                  </tr>
                </thead>
                <tbody>
                  <tr v-for="(item, index) in list" :key="item.id">
                    <td>{
         
         { index + 1 }}</td>
                    <td>{
         
         { item.name }}</td>
                    <td :class="{ red: item.price > 500 }">{
         
         { item.price.toFixed(2) }}</td>
                    <td><a @click="del(item.id)" href="javascript:;">删除</a></td>
                  </tr>
                </tbody>
                <tfoot>
                  <tr>
                    <td colspan="4">消费总计: {
         
         { totalPrice.toFixed(2) }}</td>
                  </tr>
                </tfoot>
              </table>
            </div>
            
            <!-- 右侧图表 -->
            <div class="echarts-box" id="main"></div>
          </div>
        </div>
        <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/echarts.min.js"></script>
        <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
        <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
        <script>
          /**
           * 接口文档地址:
           * https://www.apifox.cn/apidoc/shared-24459455-ebb1-4fdc-8df8-0aff8dc317a8/api-53371058
           * 
           * 功能需求:
           * 1. 基本渲染
           *    (1) 立刻发送请求获取数据 created
           *    (2) 拿到数据,存到data的响应式数据中
           *    (3) 结合数据,进行渲染 v-for
           *    (4) 消费统计 => 计算属性
           * 2. 添加功能
           *    (1) 收集表单数据 v-model
           *    (2) 给添加按钮注册点击事件,发送添加请求
           *    (3) 需要重新渲染
           * 3. 删除功能
           *    (1) 注册点击事件,传参传 id
           *    (2) 根据 id 发送删除请求
           *    (3) 需要重新渲染
           * 4. 饼图渲染
           *    (1) 初始化一个饼图 echarts.init(dom)  mounted钩子实现
           *    (2) 根据数据实时更新饼图 echarts.setOption({ ... })
           */
          const app = new Vue({
            el: '#app',
            data: {
              list: [],
              name: '',
              price: ''
            },
            computed: {
              totalPrice () {
                return this.list.reduce((sum, item) => sum + item.price, 0)
              }
            },
            created () {
              // const res = await axios.get('https://applet-base-api-t.itheima.net/bill', {
              //   params: {
              //     creator: '小黑'
              //   }
              // })
              // this.list = res.data.data
    
              this.getList()
            },
            mounted () {
              this.myChart = echarts.init(document.querySelector('#main'))
              this.myChart.setOption({
                // 大标题
                title: {
                  text: '消费账单列表',
                  left: 'center'
                },
                // 提示框
                tooltip: {
                  trigger: 'item'
                },
                // 图例
                legend: {
                  orient: 'vertical',
                  left: 'left'
                },
                // 数据项
                series: [
                  {
                    name: '消费账单',
                    type: 'pie',
                    radius: '50%', // 半径
                    data: [
                      // { value: 1048, name: '球鞋' },
                      // { value: 735, name: '防晒霜' }
                    ],
                    emphasis: {
                      itemStyle: {
                        shadowBlur: 10,
                        shadowOffsetX: 0,
                        shadowColor: 'rgba(0, 0, 0, 0.5)'
                      }
                    }
                  }
                ]
              })
            },
    
            methods: {
              async getList () {
                const res = await axios.get('https://applet-base-api-t.itheima.net/bill', {
                  params: {
                    creator: '小黑'
                  }
                })
                this.list = res.data.data
    
                // 更新图表
                this.myChart.setOption({
                  // 数据项
                  series: [
                    {
                      // data: [
                      //   { value: 1048, name: '球鞋' },
                      //   { value: 735, name: '防晒霜' }
                      // ]
                      data: this.list.map(item => ({ value: item.price, name: item.name}))
                    }
                  ]
                })
              },
              async add () {
                if (!this.name) {
                  alert('请输入消费名称')
                  return
                }
                if (typeof this.price !== 'number') {
                  alert('请输入正确的消费价格')
                  return
                }
    
                // 发送添加请求
                const res = await axios.post('https://applet-base-api-t.itheima.net/bill', {
                  creator: '小黑',
                  name: this.name,
                  price: this.price
                })
                // 重新渲染一次
                this.getList()
    
                this.name = ''
                this.price = ''
              },
              async del (id) {
                // 根据 id 发送删除请求
                const res = await axios.delete(`https://applet-base-api-t.itheima.net/bill/${id}`)
                // 重新渲染
                this.getList()
              }
            }
          })
        </script>
      </body>
    </html>
    

注意事项

1-如果在箭头函数中需要返回一个对象,那么需要在{}外在加上一对()

data: this.list.map(item => ({ value: item.price, name: item.name }))



工程化开发入门

工程化开发和脚手架

工程化开发模式

概念

核心包传统开发模式:基于html / css / js 文件,直接引入核心包,开发 Vue。

工程化开发模式:基于构建工具(例如:webpack)的环境中开发Vue

Untitled

根据上图,通过工程化开发模式能够将不同的源码转成不同版本的js代码

工程化开发模式优点

提高编码效率,比如使用JS新语法、Less/Sass、Typescript等通过webpack都可以编译成浏览器识别的ES3/ES5/CSS等

工程化开发模式问题

  • webpack配置不简单
  • 雷同的基础配置
  • 缺乏统一的标准

为了解决以上问题,所以我们需要一个工具,生成标准化的配置


脚手架 Vue-cli

**概念:**vue-cli是 vue 官方提供的、快速生成 vue 工程化项目的工具。【集成了webpack配置】

特点

  1. 开箱即用,零配置

  2. 内置babel等工具

  3. 标准化的webpack配置

    标准化:你的不同项目可以使用相同的架子

vue-cli 的中文官网首页:https://cli.vuejs.org/zh/



Vue-cli使用步骤

安装 vue-cli

vue-cli 是基于 Node.js 开发出来的工具,因此需要使用 npm 将它安装为全局可用的工具:

# 全局安装 vue-cli
npm install -g @vue/cli

# 查看 vue-cli 的版本,检查 vue-cli 是否安装成功
vue --version

解决 Windows PowerShell 不识别 vue 命令的问题:

默认情况下,在PowerShell 中执行 vue --version 命令会提示如下的错误消息:

Untitled

解决方案如下:

① 以管理员身份运行 PowerShell

② 执行 set-ExecutionPolicy RemoteSigned 命令

③ 输入字符 Y ,回车即可

Untitled


创建项目

vue-cli 提供了创建项目的两种方式:

# 基于 [命令行] 的方式创建 vue 项目
vue create 项目名称

# OR

# 基于 [可视化面板] 创建 vue 项目
vue ui

基于 vue ui 创建 vue 项目

步骤1:在终端下运行 vue ui 命令,自动在浏览器中打开创建项目的可视化面板:

Untitled

步骤2:在详情页面填写项目名称

Untitled

步骤3:在预设页面选择手动配置项目:

Untitled

步骤4:在功能页面勾选需要安装的功能(Choose Vue Version、Babel、CSS 预处理器、使用配置文件):

Untitled

注意:最后一项是把插件的配置信息单独存放到一个配置文件

步骤5:在配置页面勾选 vue 的版本和需要的预处理器:

Untitled

步骤6:将刚才所有的配置保存为预设(模板),方便下一次创建项目时直接复用之前的配置:

Untitled

步骤7:创建项目并自动安装依赖包:

Untitled

vue ui 的本质:通过可视化的面板采集到用户的配置信息后,在后台基于命令行的方式自动初始化项目:

Untitled

注意:预设在.vuerc配置文件中

项目创建完成后,自动进入项目仪表盘:

Untitled



基于命令行创建 vue 项目

步骤1:在终端下运行 vue create demo2 命令,基于交互式的命令行创建 vue 的项目:

Untitled

步骤2:选择要安装的功能:

Untitled

步骤3:使用上下箭头选择 vue 的版本,并使用回车键确认选择:

Untitled

步骤4:使用上下箭头选择要使用的 css 预处理器,并使用回车键确认选择:

Untitled

步骤5:使用上下箭头选择如何存储插件的配置信息,并使用回车键确认选择

Untitled

步骤6:是否将刚才的配置保存为预设:

Untitled

步骤7:选择如何安装项目中的依赖包

Untitled

步骤8:开始创建项目并自动安装依赖包

Untitled

步骤9:项目创建完成:

Untitled


总结安装流程

流程

  1. 全局安装(只需安装一次即可) yarn global add @vue/cli 或者 npm i @vue/cli -g
  2. 查看vue/cli版本: vue --version
  3. 创建项目架子:vue create project-name(项目名不能使用中文)
  4. 启动项目:yarn serve 或者 npm run serve(命令不固定,找package.json)

根据下图中的红框处修改:

Untitled

理解

前两步搞定之后,以后创建Vue项目就只需要3和4两步来创建项目了



项目目录介绍和运行流程

项目目录介绍

Untitled

虽然脚手架中的文件有很多,目前咱们只需认识三个文件即可

main.js 入口文件

App.vue App根组件

index.html 模板文件

运行流程及作用

main.js作用:导入 App.vue, 并基于 App.vue 创建结构渲染 index.html

App.vue:决定页面的呈现内容

Untitled

main.js文件详解

// 1. 导入 Vue 核心包
import Vue from 'vue'
// 2. 导入 App.vue 根组件
import App from './App.vue'

// 提示:当前处于什么环境(生产环境 / 开发环境)
Vue.config.productionTip = false

// 3. Vue实例化,提供render方法 -> 基于App.vue创建结构并渲染index.html
new Vue({
  render: h => h(App),
}).$mount('#app')

补充

提示环境信息:

Untitled

关于Vue实例化的理解:下面两种方式的效果是一致的

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

render的完全写法:

render: h => h(App),
render: (creatElement) => {
  return creatElement(App)
}


组件化开发思想

组件化概念:

概念:一个页面可以拆分成一个个组件,每个组件有着自己独立的结构、样式、行为。

好处:便于维护,利于复用 → 提升开发效率。

组件分类:普通组件、根组件。

比如:下面这个页面,若把所有的代码都写在一个页面中会显得代码比较混乱,难易维护。

咱们可以按模块进行组件划分:

Untitled

案例网站http://www.ibootstrap.cn/

Untitled

理解:比如我们把轮播图封装成一个组件,哪个项目需要用,我们就导入到哪个项目中去。

vue 中的组件化开发

vue 是一个完全支持组件化开发的框架。vue 中规定组件的后缀名是 .vue。之前接触到的 App.vue 文件本质上就是一个 vue 的组件。


根组件概念:

概念:整个应用最上层的组件,包裹所有普通小组件

Untitled

语法高亮插件:Vetur

组件构成

template:结构 (有且只能一个根元素)

script:js逻辑

style: 样式 (可支持less,需要装包)

其中,每个组件中必须包含 template 模板结构,而 script 行为和 style 样式是可选的组成部分。

让组件支持less:

(1) style标签,lang=“less” 开启less功能

(2) 装包: yarn add less less-loader -D 或者npm i less less-loader -D

Untitled




组件的构成:

组件的 template 节点

vue 规定:每个组件对应的模板结构,需要定义到节点中。

<template>
	<!-- 当前组件的DOM结构,需要定义到template标签的内部>
</template>

注:是vue提供的容器标签,只起到包裹性质的作用,它不会被渲染为真正的DOM元素


在 template 中使用指令

在组件的节点中,支持使用前面所学的指令语法,来辅助开发者渲染当前组件的 DOM 结构。

在 template 中定义根节点

在 vue 2.x 的版本中,节点内的DOM结构仅支持单个根节点:

代码示例如下:

<template>
  <div>
    <h1>App根组件</h1>
    <p>{
   
   { num }}</p>
  </div>
</template>

理解:如图所示,就是在h1 h2的外面得需要包裹一个div,否则h1 h2就算两个根节点

但是 ,在 vue 3.x 的版本中,中支持定义多个根节点:

<template>
    <h1>App根组件</h1>
    <h2>这是副标题</h2>
</template>

理解:在h1 h2的外面就算没有包裹一个统一的根节点(比如用div包裹他们两)也没有问题


总结:

总结:template节点

  1. template里面支持使用vue的指令来渲染DOM结构
  2. template不会被渲染成真正的DOM结构,它只起到包裹性质的作用
  3. 在template内部,vue3支持多个根结点,vue2仅支持单个根节点


组件的 script 节点

vue 规定:组件内的


---

### **script 中的 name 节点**

可以通过 name 节点为当前组件定义一个名称:

```jsx
<script>
export default {
	// name 属性指向带式当前组件的名称(建议: 每个单词的首字母大写)
  name: 'App',
}
</script>

在使用 vue-devtools 进行项目调试的时候,自定义的组件名称可以清晰的区分每个组件:

Untitled


script 中的 data 节点

vue 组件渲染期间需要用到的数据,可以定义在 data 节点中:

<script>
export default {
  name: 'App',
	// 组件的数据(data方法中return出去的对象,就是当前组件渲染期间需要用到的数据对象)
  data() {
    return {
      username: 'test'
    }
  }
}
</script>

组件中的 data 必须是函数

vue 规定:组件中的 data 必须是一个函数,不能直接指向一个数据对象。因此在组件中定义 data 数据节点时,下面的方式是错误的:

data: { //组件中,不能直接让 data 指向一个数据对象(会报错)
		count: 0
}

script 中的 methods 节点

组件中的事件处理函数,必须定义到 methods 节点中,示例代码如下:

export default {
  name: 'App',
  data() {
    return {
      count: 0,
    }
  },
  methods: {
    addCount() {
      this.count++
    }
  }
}

methods指向一个对象,在methods指向的对象中,我们可以声明对应的事件处理函数,在事件处理函数中,this指向当前组件的实例。值得注意的是,当前组件中的数据(如data),可以通过this访问,如图中,我们可以通过this.count++来让实例中的data中的count自增加一。



组件的 style 节点

vue 规定:组件内的


其中 <style> 标签上的 lang="css" 属性是可选的,它表示所使用的样式语言。默认只支持普通的 css 语法,可选值还有 less、scss 等。

---

### 让 style 中支持 less 语法

如果希望使用 less 语法编写组件的 style 样式,可以按照如下两个步骤进行配置:

① 运行 npm install less -D 命令安装依赖包,从而提供 less 语法的编译支持

② 在 <style> 标签上添加 lang="less" 属性,即可使用 less 语法编写组件的样式

```jsx
<style lang="less">
h1 {
	font-weight: normal;
	i {
		color: red;
		font-style: normal;
	}
}
</style>

个人总结:

vue组件的组成结构

每个 .vue 组件都由 3 部分构成,分别是:

template -> 组件的模板结构 script -> 组件的 JavaScript 行为 style -> 组件的样式

其中,每个组件中必须包含 template 模板结构,而 script 行为和 style 样式是可选的组成部分。

组件的 template 节点

vue 规定:每个组件对应的模板结构,需要定义到节点中。

<template>
	<!-- 当前组件的DOM结构,需要定义到template标签的内部>
</template>
  1. template里面支持使用vue的指令来渲染DOM结构
  2. template不会被渲染成真正的DOM结构,它只起到包裹性质的作用
  3. 在template内部,vue3支持多个根结点,vue2仅支持单个根节点

组件的 script 节点

组件内的

<script>
	export default {
		// 组件的名称
		name: 'MyApp',
		// 组件的数据
		data() {
			return {
				username: 'Jack'
			}
		},
		//组件的方法
		methods: {
			addCount() {
				this.count++
			}
		}
	}
</script>

export default:

组件相关的 data 数据,methods 方法等,都需要定义到 export default 所导出的对象中。

data() :

data方法中return 出去的对象,就是当前组件渲染期间所需要用到的数据对象

组件的 style 节点

组件内的

<style lang="css">
	h1 {
		font-weight: normal;
	}
</style>



组件的基本使用

组件的注册

概念:组件之间可以进行相互的引用,例如:vue 中组件的引用原则:先注册后使用。

Untitled

vue 中组件的引用原则:先注册后使用。

使用方式:当成html标签使用即可 <组件名></组件名>

命名规范:大驼峰命名法, 如 HmHeader


组件注册命名方式:

在进行组件的注册时,定义组件注册名称的方式有两种:

① 使用 kebab-case 命名法(俗称短横线命名法,例如 my-swiper 和 my-search)

② 使用 PascalCase 命名法(俗称帕斯卡命名法或大驼峰命名法,例如 MySwiper 和 MySearch)

短横线命名法的特点:

必须严格按照短横线名称进行使用

帕斯卡命名法的特点:

既可以严格按照帕斯卡名称进行使用,又可以转化为短横线名称进行使用

注意:在实际开发中,推荐使用帕斯卡命名法为组件注册名称,因为它的适用性更强。

案例:

//kebab-case
app.component('my-swiper', Swiper)
//PascalCase
app.component('MyTest', Test)

补充:

在template中写组件采用tab快速补齐

<HmHeader></HmHeader>

Untitled


注册组件的两种方式:

vue 中注册组件的方式分为“全局注册”和“局部注册”两种,其中:

被全局注册的组件,可以在全局任何一个组件内使用

被局部注册的组件,只能在当前注册的范围内使用

Untitled

如图,在全局注册的组件 Swiper,既可以在Home组件中使用,也可以在About组件中使用,

而在Home组件中局部注册的组Search组件,只能在Home组件中使用。


全局注册组件

全局注册组件方式app.component(’被注册的组件名称’, 组件)

在main.js中操作

使用app.component进行注册

Untitled

或者,使用Vue.component进行注册

Untitled


使用全局注册组件

使用 app.component() 方法注册的全局组件,直接以标签的形式进行使用即可,例如:

Untitled

按照上面的说明配置main.js和App.vue

Untitled


局部注册组件

操作流程:

在组件App中导入组件search, 然后在components中注册组件search

Untitled

具体操作:

Untitled


全局注册和局部注册的区别

被全局注册的组件,可以在全局任何一个组件内使用

被局部注册的组件,只能在当前注册的范围内使用

应用场景:

如果某些组件在开发期间的使用频率很高,推荐进行全局注册;

如果某些组件只在特定的情况下会被用到,推荐进行局部注册。


通过 name 属性注册组件

在注册组件期间,除了可以直接提供组件的注册名称之外,还可以把组件的 name 属性作为注册后组件的名称,示例代码如下:

Untitled

注意事项:

如果注册组件的命名与要导入的组件一致,我们可以省略组件的命名:

如下所示,如果我们需要注册MyList组件,我们可以往components中填入:

components: {
	'MyList': MyList
}

由于组件的命名与导入组件的名称一致,所以可以简化为如下形式:

components: {
	MyList
}

案例:

Untitled



个人总结:

组件的注册:先注册,后使用

注册方式:全局注册和局部注册

组件注册名称:推荐用帕斯卡命名法

//PascalCase
app.component('MyTest', Test)

全局注册组件:被全局注册的组件,可以在全局任何一个组件内使用

Untitled

局部注册组件:被局部注册的组件,只能在当前注册的范围内使用

Untitled

解决样式冲突问题

默认情况下,写在 .vue 组件中的样式会全局生效,因此很容易造成多个组件之间的样式冲突问题

为每个组件分配唯一的自定义属性,在编写组件样式时,通过属性选择器来控制样式的作用域:

Untitled

防止组件间样式冲突:

<style scoped> </style>

组件的 props:

props 是组件的自定义属性,使用者通过 props 把数据传递到子组件内部供使用。

作用:父组件通过 props 向子组件传递数据

示例如下:

// 这是父组件 App.vue
<template>
  <div>
    <h1>这是 App.vue 根组件</h1>
    <hr>
    <!-- 通过自定义属性props,把文章标题和作者,传送到 my-article 组件中 -->
    <my-article :title="info.title" :author="info.author" :MyTest="info.MyTest"></my-article>
  </div>
</template>

<script>
import MyArticle from './Article.vue'
export default {
  name: 'MyApp',
  data() {
    return {
      info: {
        title: 'abc',
        author: '123',
        MyTest: 'test'
      }
    }
  },
  components: {
    MyArticle,
  }
}
</script>
// 这是子组件 Article.vue
<template>
  <div>
    <h3>标题:{
   
   {title}}</h3>
    <h5>作者:{
   
   {author}}</h5>
    <h6>发布时间:{
   
   {pubTime}}</h6>
  </div>
</template>

<script>
export default {
  name: 'MyArticle',
  // 外界可以传递指定的数据,到当前的组件中
  props: ['title', 'author', 'pubTime']
}
</script>

Class 与 Style 绑定

vue 允许开发者通过 v-bind 属性绑定指令,为元素动态绑定 class 属性的值和行内的 style 样式




综合案例-小兔仙首页

拆分模块-局部注册

Untitled

这里单独讲个模块:新鲜好物模块

Untitled

我们把商品项拆成模块,并进行全局注册复用:

Untitled

先搞定商品模块的代码:

  • Code:

    <template>
      <div>
        <li class="base-goods-item">
          <a href="#">
            <div class="pic"><img src="@/assets/images/goods1.png" alt="" /></div>
            <div class="txt">
              <h4>KN95级莫兰迪色防护口罩</h4>
              <p>¥ <span>79</span></p>
            </div>
          </a>
        </li>
      </div>
    </template>
    
    <script>
    export default {
    
    }
    </script>
    
    <style>
    base-goods-item li {
      width: 304px;
      height: 404px;
      background-color: #EEF9F4;
    }
    base-goods-item li {
      display: block;
    }
    base-goods-item li .pic {
      width: 304px;
      height: 304px;
    }
    base-goods-item li .txt {
      text-align: center;
    }
    base-goods-item li h4 {
      margin-top: 17px;
      margin-bottom: 8px;
      font-size: 20px;
    }
    base-goods-item li p {
      font-size: 18px;
      color: #AA2113;
    }
    base-goods-item li p span {
      font-size: 22px;
    }
    </style>
    

再在main.js中进行全局注册:

import BaseGoodsItem from './components/BaseGoodsItem.vue'
Vue.component('BaseGoodsItem', BaseGoodsItem)

最后在XtxNewGoods.vue中进行使用:

<ul>
  <BaseGoodsItem></BaseGoodsItem>
  <BaseGoodsItem></BaseGoodsItem>
  <BaseGoodsItem></BaseGoodsItem>
  <BaseGoodsItem></BaseGoodsItem>
</ul>

补充:多次生成组件

<BaseGoodsItem v-for="item in 5" :key="item"></BaseGoodsItem>

注:这里的item是从1开始的数字

猜你喜欢

转载自blog.csdn.net/CaptainDrake/article/details/132099278