Vue2

153 阅读13分钟

Vue2官网(引包):v2.cn.vuejs.org/
Vue3官网:cn.vuejs.org/
导入vue: cdn.jsdelivr.net/npm/vue@2/d…
饼图插件: cdn.jsdelivr.net/npm/echarts…
饼图echarts官网:echarts.apache.org/zh/index.ht…
Vue路由插件:v3.router.vuejs.org/zh/
JavaScript Standard Style 规范说明:standardjs.com/rules-zhcn.…
[ESLint 规则表]:ctrl+F 查代码语法错误 zh-hans.eslint.org/docs/latest…
快速搭建一个后端接口服务器(后端还没有提供接口时):www.npmjs.com/package/jso…
Vue 移动端常用组件库:vant-ui:vant-contrib.gitee.io/vant/v2/#/z…
Vue PC端常用组件库:element-ui: element.eleme.cn/#/zh-CN

vue是通过创建项目文件后的local地址运行页面 在项目终端里:npm run serve

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

一、

image.png

1.1 Vue快速上手

1.1.1 Vue概念

image.png

image.png

image.png

image.png

1.1.2 创建实例

image.png

image.png

image.png

image.png

image.png

image.png

1.1.3 差值表达式

image.png

image.png

image.png

image.png

image.png

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <p>{{ nikename }}</p>
        <p>{{ nikename.toUpperCase() }}</p>
        <p>{{ age>18? '成年':'未成年' }}</p>
        <p>{{ friend.name }} </p>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>

    <script>
        const app = new Vue({
            el: '#app',
            data: {
                nikename: 'tony',
                age: 18,
                friend: {
                    name: 'json',
                    hobby: '热爱学习Vue'
                }
            }
        })
    </script>
</body>

</html>

image.png

1.1.4 响应式

image.png

image.png

image.png

image.png

1.1.5 开发者工具

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

插件安装失败的话,检查下在运行代项目的网页里(用户界面)有没有安装好插件

image.png

image.png

image.png

1.2 Vue指令

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

1.3 综合案例-小黑记事本

image.png

image.png

image.png

二、Vue核心技术与实战

image.png

image.png

2.1 指令补充

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

2.2 computed计算属性

image.png

image.png

image.png

image.png

image.png

image.png

<!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">
    姓:<input v-model="firstName" type="text"><br>
    名:<input v-model="lastName" type="text"><br>
    <p>姓名:{{ fullName }} </p>
    <button @click="changeName()">修改姓名</button>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        firstName: '吕',
        lastName: '布'
      },
      computed: {
        // 属性名没有参数
        fullName: {
          // 作为类似的methods方法,提供函数功能,传递值
          // (1) 当fullName计算属性, 被获取求值时,执行get (有缓存会将返回值作为,求值的结果)
          get() {
            return this.firstName + this.lastName
          },
          // 获取fullName的值,对其做修改,修改值
          // (2) 当fullName计算属性, 被修改赋值时,执行set修改的值,传递给set方法的形参
          set(value) {
            // console.log(value.slice(0,1))
            // console.log(value.slice(1))
            // 把修改的值渲染到页面
            // sclice(0,1):从0开始,截取一个字符串
            this.firstName = value.sclice(0, 1)
            this.lastName = value.sclice(1)
          }
        }

      },
      methods: {
        // fullName(){
        //   return this.firstName+this.lastName
        // },
        changeName() {
          this.fullName = '吕小布'
        }
      }
    })
  </script>
</body>

</html>

image.png

image.png

image.png

<!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" />
  <link rel="stylesheet" href="./styles/index.css" />
  <title>Document</title>
</head>

<body>
  <div id="app" class="score-case">
    <div class="table">
      <table>
        <thead>
          <tr>
            <th>编号</th>
            <th>科目</th>
            <th>成绩</th>
            <th>操作</th>
          </tr>
        </thead>
        <tbody v-if="list.length > 0">
          <tr v-for="(item,index) in list" :key="item.id">
            <!-- 编号最好用index,不用id -->
            <td> {{ index+1 }} </td>
            <td> {{ item.subject }} </td>
            <!-- <td class="red"> {{ item.score }} </td> -->
            <!-- :class="{red:false}" 动态设置类,单个类用{},多个类用[] -->
            <!-- 成绩60分,分数标红 -->
            <td :class="{red : item.score < 60}"> {{ item.score }} </td>
            <!-- @click.prevent 阻止默认跳转行为 -->
            <td><a @click.prevent="del(item.id)" href="http://www.baidu.com">删除</a></td>
          </tr>

        </tbody>
        <tbody v-else>
          <tr>
            <td colspan="5">
              <span class="none">暂无数据</span>
            </td>
          </tr>
        </tbody>

        <tfoot>
          <tr>
            <td colspan="5">
              <span>总分:{{ totalscore }} </span>
              <span style="margin-left: 50px">平均分: {{ averagescore }} </span>
            </td>
          </tr>
        </tfoot>
      </table>
    </div>
    <div class="form">
      <div class="form-item">
        <div class="label">科目:</div>
        <div class="input">
          <input v-model.trim="subject" type="text" placeholder="请输入科目" />
        </div>
      </div>
      <div class="form-item">
        <div class="label">分数:</div>
        <div class="input">
          <input v-model.number="score" type="text" placeholder="请输入分数" />
        </div>
      </div>
      <div class="form-item">
        <div class="label"></div>
        <div class="input">
          <button @click="add()" class="submit">添加</button>
        </div>
      </div>
    </div>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>

  <script>
    const app = new Vue({
      el: '#app',
      data: {
        list: [
          { id: 1, subject: '语文', score: 60 },
          { id: 7, subject: '数学', score: 99 },
          { id: 12, subject: '英语', score: 70 },
        ],
        subject: '',
        score: ''
      },
      computed: {
        // 求总分:对数组求和
        totalscore() {
          return this.list.reduce((sum, item) => sum + item.score, 0)
        },
        // 求平均分
        // toFixed(2):保留几位小数
        averagescore(){
          if(this.list.length === 0){
            return 0
          }
          return (this.totalscore/this.list.length).toFixed(2)
        }
      },
      methods: {
        // 删除 
        del(id) {
          // 过滤与id相等的行,留下的是个新数组 赋值给原数组
          this.list = this.list.filter(item => item.id !== id)
        },
        // 添加成绩单
        add() {
          // 功能优化:表单为空,不能添加数据
          if (!this.subject) {
            alert('请输入科目!')
          }
          if (typeof this.score !== 'number') {
            alert('请输入正确的成绩!')
          }
          // 给数组里面追加数据 
          // 在数组前追加unshift,在数组后追加push
          this.list.unshift({
            id: +new Date(),
            subject: this.subject,
            score: this.score
          })
          // 功能优化:添加完表单数据 清空表单
          this.subject = ''
          this.score = ''
        }
      }
    })
  </script>
</body>

</html>

2.3 watch侦听器

image.png

image.png

<!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;
      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;
    }
  </style>
</head>

<body>
  <div id="app">
    <!-- 条件选择框 -->
    <div class="query">
      <span>翻译成的语言:</span>
      <select v-model="obj.lang">
        <option value="italy">意大利</option>
        <option value="english">英语</option>
        <option value="german">德语</option>
      </select>
    </div>

    <!-- 翻译框 -->
    <div class="box">
      <div class="input-wrap">
        <textarea v-model="obj.words"></textarea>
        <span><i>⌨️</i>文档翻译</span>
      </div>
      <div class="output-wrap">
        <div class="transbox"> {{ result }} </div>
      </div>
    </div>
  </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>
    // 接口地址:https://applet-base-api-t.itheima.net/api/translate
    // 请求方式:get
    // 请求参数:
    // (1)words:需要被翻译的文本(必传)
    // (2)lang: 需要被翻译成的语言(可选)默认值-意大利
    // -----------------------------------------------

    const app = new Vue({
      el: '#app',
      data: {
        // words: '',
        obj: {
          words: '',
          lang: 'italy',//默认下拉菜单是意大利
        },
        // 渲染翻译结果到页面中
        result: '',
        // timer: '' // 定时器
      },
      // 具体讲解:(1) watch语法 (2) 具体业务实现
      // 批量监视写法
      watch: {
        obj: {
          deep: true, //批量监视
          immediate: true, // 立即触发
          // 处理函数
          handler(newValue) {
            // console.log('变化了', newValue)
            // 关定时器
            clearTimeout(this.timer)
            // 每输入个文字,就会发起一次请求,性能优化
            // 防抖:延迟执行→干啥事先等一等,延迟一会,一段时间内没有反应,再执行
            // 如果一个属性不需要在页面渲染出来,可以直接挂载到实例上,作为实例属性用
            this.timer = setTimeout(async () => {
              const res = await axios({
                url: 'https://applet-base-api-t.itheima.net/api/translate',
                // params: {
                //   words: newvalue
                //   lang:newValue
                // }
                params: newValue
              })
              // 把模拟的翻译结果渲染到页面中
              this.result = res.data.data
              // 打印结果
              // 定时器一般300ms用户体验最好
              console.log(res.data.data) // 会得到一个模拟的翻译结果
            }, 300)
          }
        }
      }


      // watch: {
      //   // 1.监视words
      //   //该方法会在数据变化时调用执行
      //   //两个参数: newValue新值, oldValue老值 (一般不用)

      //   // words(newvalue, oldvalue) {
      //   //   console.log('变化了', newvalue,oldvalue)
      //   // } 

      //   // 2.监视对象里面的属性
      //   // async await 发起请求
      //   "obj.words"(newvalue, oldvalue) {
      //     // console.log('变化了', newvalue, oldvalue)
      //     // 关定时器
      //     clearTimeout(this.timer)
      //     // 每输入个文字,就会发起一次请求,性能优化
      //     // 防抖:延迟执行→干啥事先等一等,延迟一会,一段时间内没有反应,再执行
      //     // 如果一个属性不需要在页面渲染出来,可以直接挂载到实例上,作为实例属性用
      //     this.timer = setTimeout(async () => {
      //       const res = await axios({
      //         url: 'https://applet-base-api-t.itheima.net/api/translate',
      //         params: {
      //           words: newvalue
      //         }
      //       })
      //       // 把模拟的翻译结果渲染到页面中
      //       this.result = res.data.data
      //       // 打印结果
      //       // 定时器一般300ms用户体验最好
      //       console.log(res.data.data) // 会得到一个模拟的翻译结果
      //     }, 300)
      //     // async必须直接包含await
      //     // 获取数据

      //   }
      // }

    })
  </script>
</body>

</html>

image.png

<!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;
        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;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <!-- 条件选择框 -->
      <div class="query">
        <span>翻译成的语言:</span>
        <select v-model="obj.lang">
          <option value="italy">意大利</option>
          <option value="english">英语</option>
          <option value="german">德语</option>
        </select>
      </div>

      <!-- 翻译框 -->
      <div class="box">
        <div class="input-wrap">
          <textarea v-model="obj.words"></textarea>
          <span><i>⌨️</i>文档翻译</span>
        </div>
        <div class="output-wrap">
          <div class="transbox">{{ result }}</div>
        </div>
      </div>
    </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>
      // 需求:输入内容,修改语言,都实时翻译

      // 接口地址:https://applet-base-api-t.itheima.net/api/translate
      // 请求方式:get
      // 请求参数:
      // (1)words:需要被翻译的文本(必传)
      // (2)lang: 需要被翻译成的语言(可选)默认值-意大利
      // -----------------------------------------------
   
      const app = new Vue({
        el: '#app',
        data: {
          obj: {
            words: '小黑',
            lang: 'italy'
          },
          result: '', // 翻译结果
        },
        watch: {
          obj: {
            deep: true, // 深度监视
            immediate: true, // 立刻执行,一进入页面handler就立刻执行一次
            handler (newValue) {
              clearTimeout(this.timer)
              this.timer = setTimeout(async () => {
                const res = await axios({
                  url: 'https://applet-base-api-t.itheima.net/api/translate',
                  params: newValue
                })
                this.result = res.data.data
                console.log(res.data.data)
              }, 300)
            }
          }


          // 'obj.words' (newValue) {
          //   clearTimeout(this.timer)
          //   this.timer = setTimeout(async () => {
          //     const res = await axios({
          //       url: 'https://applet-base-api-t.itheima.net/api/translate',
          //       params: {
          //         words: newValue
          //       }
          //     })
          //     this.result = res.data.data
          //     console.log(res.data.data)
          //   }, 300)
          // }
        }
      })
    </script>
  </body>
</html>

2.4 综合案例:水果购物车

image.png

image.png

image.png

image.png

image.png

image.png

image.png

<!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" />
  <link rel="stylesheet" href="./css/inputnumber.css" />
  <link rel="stylesheet" href="./css/index.css" />
  <title>购物车</title>
</head>

<body>
  <div class="app-container" id="app">
    <!-- 顶部banner -->
    <div class="banner-box"><img src="http://autumnfish.cn/static/fruit.jpg" alt="" /></div>
    <!-- 面包屑 -->
    <div class="breadcrumb">
      <span>🏠</span>
      /
      <span>购物车</span>
    </div>
    <!-- 购物车主体 -->
    <div class="main" v-if="fruitList.length > 0">
      <div class="table">
        <!-- 头部 -->
        <div class="thead">
          <div class="tr">
            <div class="th">选中</div>
            <div class="th th-pic">图片</div>
            <div class="th">单价</div>
            <div class="th num-th">个数</div>
            <div class="th">小计</div>
            <div class="th">操作</div>
          </div>
        </div>
        <!-- 身体 -->
        <div class="tbody">
          <!-- 频繁的动态控制active类名 用class,对象设置值 -->
          <div v-for="(item,index) in fruitList" :key="item.id" class="tr" :class="{active:item.isChecked}">
            <!-- 处理表单:v-model -->
            <div class="td"><input type="checkbox" v-model="item.isChecked" /></div>
            <div class="td"><img :src="item.icon" alt="" /></div>
            <div class="td"> {{ item.price }} </div>
            <div class="td">
              <div class="my-input-number">
                <!-- 动态控制“:”,禁用:disabled=‘布尔值’ -->
                <button class="decrease" :disabled="item.num <= 1" @click="sub(item.id)"> - </button>
                <span class="my-input__inner"> {{ item.num }} </span>
                <button class="increase" @click="add(item.id)"> + </button>
              </div>
            </div>
            <div class="td"> {{ item.num * item.price }} </div>
            <!-- 点击删除用“过滤” -->
            <div class="td"><button @click="del(item.id)">删除</button></div>
          </div>


        </div>
      </div>
      <!-- 底部 -->
      <div class="bottom">
        <!-- 全选 -->
        <label class="check-all">
          <input type="checkbox" v-model="isAll" />
          全选
        </label>
        <div class="right-box">
          <!-- 所有商品总价 -->
          <span class="price-box">总价&nbsp;&nbsp;:&nbsp;&nbsp;¥&nbsp;<span class="price"> {{ totalprice }} </span></span>
          <!-- 结算按钮 -->
          <button class="pay">结算( {{ totalCount }} )</button>
        </div>
      </div>
    </div>
    <!-- 空车 -->
    <div class="empty" v-else>🛒空空如也</div>
  </div>
  <script src="../vue.js"></script>
  <script>
    const defaultArr = [
          {
            id: 1,
            icon: 'http://autumnfish.cn/static/火龙果.png',
            isChecked: true,
            num: 2,
            price: 6,
          },
          {
            id: 2,
            icon: 'http://autumnfish.cn/static/荔枝.png',
            isChecked: false,
            num: 7,
            price: 20,
          },
          {
            id: 3,
            icon: 'http://autumnfish.cn/static/榴莲.png',
            isChecked: false,
            num: 3,
            price: 40,
          },
          {
            id: 4,
            icon: 'http://autumnfish.cn/static/鸭梨.png',
            isChecked: true,
            num: 10,
            price: 3,
          },
          {
            id: 5,
            icon: 'http://autumnfish.cn/static/樱桃.png',
            isChecked: false,
            num: 20,
            price: 34,
          },
        ]
    const app = new Vue({
      el: '#app',
      data: {
        // 水果列表
        // 从本地存储里读取数据,转换成parse对象
        // 防止用户把缓存的list清空,一般给个'空数组'的初始值,
        // fruitList: JSON.parse(localStorage.getItem('list')) || []
        fruitList: JSON.parse(localStorage.getItem('list')) || defaultArr
      },

      // 统计求和 => 计算属性
      // 计算属性有返回值
      computed: {
        // 默认计算属性:只能获取不能设置,要设置需要写完整写法
        // isAll() {
        //   // 必须所有的小选按钮都选中,全选按钮才选中 -> every
        //   // 计算属性有return 值
        //   // 判断item里的所有item.isChecked === true,也就是所有小选按钮是不是都是true,返回布尔值给全选按钮
        //   // return this.fruitList.every(item => item.isChecked === true)
        //   return this.fruitList.every(item => item.isChecked)
        // }

        // 全选 反选功能:计算属性完整写法:get + set
        isAll: {
          get() {
            // 必须所有的小选按钮都选中,全选按钮才选中 -> every
            return this.fruitList.every(item => item.isChecked)
          },
          set(value) {
            // value拿到的值是大选按钮传递给isAll的布尔值,然后isAll传递给value
            // 基于拿到的布尔值,要让所有的小选框,同步状态
            // console.log(value)
            // 全选 -> forEach
            // 把大选按钮的值赋值给所有小选按钮
            return this.fruitList.forEach(item => item.isChecked = value)
          }
        },

        // “统计”的方法“reduce”
        // reduce((参数1:阶段性累加的结果,参数2:遍历的每一项) => xxx:逻辑,起始值)
        // 计算总数
        totalCount() {
          return this.fruitList.reduce((sum, item) => {
            // 如果当前小选按钮被选中 -> 累加sum
            if (item.isChecked) {
              return sum + item.num
            }
            // 如果当前小选按钮被选中 -> 返回sum
            else {
              return sum
            }
          }, 0)
        },

        // 计算总价 选中的小选按钮的 num * price
        totalprice() {
          return this.fruitList.reduce((totalMoney, item) => {
            if (item.isChecked) {
              // const money = item.num * item.price
              // 如果当前小选按钮被选中 -> 累加totalMoney
              return totalMoney + item.num * item.price
            } else {
              // 如果当前小选按钮被选中 -> 返回totalMoney
              return totalMoney
            }
          }, 0)
        }
      },

      methods: {
        del(id) {
          // 过滤数组里面item项
          this.fruitList = this.fruitList.filter(item => item.id !== id)
        },
        sub(id) {
          // 1.根据id找到数组中的对应项 -> find
          const fruit = this.fruitList.find(item => item.id === id)
          // 2.修改数组项的属性值
          // console.log(fruit)
          fruit.num--
        },
        add(id) {
          const fruit = this.fruitList.find(item => item.id === id)
          // console.log(fruit)
          fruit.num++
        }
      },

      // 监听页面数据是否变化
      // 监听所有数据,把变化的数据缓存到本地
      watch: {
        // 监听数组变化
        fruitList: {
          deep: true,
          // 处理方法
          handler(newValue) {
            // console.log(newValue)
            // 把变化的数据缓存到本地,注意要把复杂格式的newValue转换成JSON格式进行存储
            // localStorage.setItem('键',值)
            localStorage.setItem('list', JSON.stringify(newValue))
          }
        }
      }
    })

  </script>
</body>

</html>

image.png

三、Vue核心技术与实战_day03

image.png

3.1 生命周期

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

3.2 综合案例-小黑记事本 重点:e-charts图表

image.png

image.png

image.png

image.png

image.png

image.png

image.png

mounted中

image.png

image.png

image.png

<!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/bootstrap@5.1.3/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">
          <!-- 2.1 收集表单数据 v-model -->
          <!-- trim去除空格,number转数字 -->
          <input v-model.trim="name" type="text" class="form-control" placeholder="消费名称" />
          <input v-model.number="price" type="text" class="form-control" placeholder="消费价格" />
          <button type="button" @click="add" class="btn btn-primary">添加账单</button>
        </form>

        <table class="table table-hover">
          <thead>
            <tr>
              <th>编号</th>
              <th>消费名称</th>
              <th>消费价格</th>
              <th>操作</th>
            </tr>
          </thead>
          <tbody>
            <!-- 1.3 结合数据进行渲染 v-for -->
            <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>
              <!-- 3.1 注册点击事件,传参传id(知道删除那一条数据) -->
              <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>

      <!-- echarts第二步:为 ECharts 准备一个定义了宽高的 DOM -->
      <!-- 右侧图表 e-charts -->
      <div class="echarts-box" id="main"></div>
    </div>
  </div>
  <!-- echarts第一步:引入刚刚下载的 ECharts 文件 -->
  <script src="https://cdn.jsdelivr.net/npm/echarts@5.4.0/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) 插件echarts(3步),注意:在vue中,得等DOM元素渲染完,mounted钩子实现
     *  (2)根据数据实时更新饼图 echarts.setOption({ ... })
     */
    const app = new Vue({
      el: '#app',
      data: {
        // 把获取的数据存储到数组中,做页面渲染
        list: [],
        // 收集表单数据 v-model
        name: '',
        price: ''
      },

      // 1.4 消费统计 => 计算属性(求和)
      computed: {
        totalPrice() {
          // sum:总和,item.price:阶段性求和的结果
          return this.list.reduce((sum, item) => sum + item.price, 0)
        }
      },

      //1.1立刻发送请求获取数据, async await axios发送请求,async写在await直系父级上
      // 每次都要把后台数据重新渲染,所以把渲染方法封装到methods里面
      created() {
        // 立刻发送请求获取数据 created
        // const res = await axios.get('https://applet-base-api-t.itheima.net/bill', {
        //   // 参数,对象形式
        //   params: {
        //     creator: '小黑'
        //   }
        // })
        // // console.log(res)
        // // res.data.data是个数组数据
        // // 1.2 拿到数据,存到data的响应式数据中
        // this.list = res.data.data
        // 把数据重新渲染到list
        this.getList()
      },
      mounted() {
        // echarts第三步:基于准备好的dom,初始化echarts实例
        // let myChart = echarts.init(document.querySelector('#main'))
        // 利用this存储一下myChart
        this.myChart = echarts.init(document.querySelector('#main'))
        // 指定图表的配置项和数据
        this.myChart.setOption(
          // 柱状图
          //   {
          //   title: {
          //     text: 'ECharts 入门示例'
          //   },
          //   tooltip: {},
          //   legend: {
          //     data: ['销量']
          //   },
          //   xAxis: {
          //     data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
          //   },
          //   yAxis: {},
          //   series: [
          //     {
          //       name: '销量',
          //       type: 'bar',
          //       data: [5, 20, 36, 10, 10, 20]
          //     }
          //   ]
          // }

          // 饼状图
          {
            title: {
              // 标题文本
              text: '消费账单列表',
              // 子标题
              subtext: 'Fake Data',
              // 控制居中
              left: 'center'
            },
            // 提示框
            tooltip: {
              trigger: 'item'
            },
            // 图例
            legend: {
              // 垂直对齐
              orient: 'vertical',
              // 居左
              left: 'left'
            },
            // 数据项,把数据项放到methods更新图标中
            series: [
              {
                // 每个扇形属于的总名称
                name: '消费账单',
                // 图标类型:饼图
                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)'
                  }
                }
              }
            ]
          }
        );
      },

      methods: {
        async getList() {
          // 立刻发送请求获取数据 created
          const res = await axios.get('https://applet-base-api-t.itheima.net/bill', {
            // 参数,对象形式
            params: {
              creator: '小黑'
            }
          })
          this.list = res.data.data
          // 4.2发送完获取数据的请求就更新图表
          this.myChart.setOption({
            // 数据项
            series: [
              {
              // 只保留data数组
                // data: [
                //   // 数据动态渲染
                //   { value: 10, name: '毛毯' },
                //   { value: 100, name: '篮球'},            
                // ]
                // 动态更新数据项-map
                // data:this.list.map(item => ({数据的数组})) 要给对象+括号
                data:this.list.map(item => ({value:item.price,name:item.name}))
              }
            ]
          })
        },
        // 2.2给添加按钮添加点击事件,发送添加请求 方法全写在methods里面
        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', {
            // post请求参数直接传对象
            creator: '小黑',
            name: this.name,
            price: this.price
          })
          // console.log(res)
          // 2.3 重新渲染
          this.getList()
          // 清空文本框
          this.name = '',
            this.price = ''
        },
        async del(id) {
          // 获取删除信息的id
          // console.log(id)
          // 根据id删除消息
          // 动态设置id
          const res = await axios.delete(`https://applet-base-api-t.itheima.net/bill/${id}`)
          // 这时候后台数据已经被删除
          // console.log(res)
          // 重新渲染
          this.getList()
        },
      }
    })

  </script>
</body>

</html>

3.3 工程化开发入门——vue框架_CLI脚手架

image.png

image.png

image.png

W3MKQZJ7%}P@KM_0T7ZZ_MV.png

image.png

image.png

N~B[}LU8W5CA2UOK]HBMJHT.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

App.vue里面快速生成"template,script,style"框架:“<vue”

image.png

image.png

image.png

image.png

image.png

这种报错信息是因为给App.vue导入了组件,但是没有使用组件 image.png

image.png

如果使用组件标签时,Tab键出不来的话,就按照下面进行配置:设置里面搜索,配置

image.png

image.png

image.png

image.png

image.png

image.png

3.4 综合案例:小兔鲜首页

image.png

image.png

image.png

image.png

image.png

image.png

shift+alt+鼠标左键:全部选中组件,剪切;同样的办法选中注释,回车,ctrl+v,就按行粘贴

image.png

image.png

image.png

四、组件

image.png

4.1 组件的三大组成部分

image.png

image.png

image.png

image.png

image.png

image.png

image.png

4.2 组件通信

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

4.3 综合案例:小黑记事本(组件版)

image.png

image.png

(1)拆分组件

image.png

(2)渲染待办业务

image.png

(3)添加任务

image.png

(4)删除任务

image.png

(5)底部合计 和 底部清空

image.png

(6)持久化存储

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

4.4 进阶语法

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

五、封装组件

image.png

5.1定义指令

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

  <div class="main">
    <!-- 1.添加蒙层类 -->
    <!-- 2.添加指令 -->
    <div class="box loading" v-loading="isLoading">
      <ul>
        <li v-for="item 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>
    <!-- 3.指令复用,互不影响 -->
    <div class="box2" v-loading="isLoading2"></div>
  </div>
</template>

<script>
// 安装axios =>  yarn add axios
import axios from "axios";

// 接口地址:http://hmajax.itheima.net/api/news
// 请求方式:get
export default {
  data() {
    return {
      list: [],
      // 准备初始值
      isLoading: true,
      isLoading2: true
    };
  },

  async created() {
    // 1. 发送请求获取数据
    const res = await axios.get("http://hmajax.itheima.net/api/news");

    // 模拟数据请求慢
    setTimeout(() => {
      // 2.1 更新到 list 中,用于页面渲染 v-for
      this.list = res.data.data;
      // 2.2定时器到时间了 ,隐藏类loading
      this.isLoading = false;
      // this.isLoading2 = false
    }, 2000);
  },

  // 2. 开启关闭loading状态(添加移除蒙层),本质只需要添加移除类即可
  directives: {
    loading: {
      // 2.1 渲染蒙层
      inserted(el, binding) {
        //  binding.value===isLoading,true:添加蒙层类;false:移除蒙层类
        binding.value
          ? el.classList.add("loading")
          : el.classList.remove("loading");
      },
      // 2.2 更新蒙层
      update(el, binding) {
        binding.value
          ? el.classList.add("loading")
          : el.classList.remove("loading");
      },
    },
  },
};
</script>

<style>
/* 1.准备一个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;
}
</style>

5.2插槽

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

5.3综合案例-商品列表

image.png

(1)标签组件封装-父组件和子组件之间的数据传输,指令

image.png

image.png

App.vue

<template>
  <div class="table-case">
    <table class="my-table">
      <thead>
        <tr>
          <th>编号</th>
          <th>名称</th>
          <th>图片</th>
          <th width="100px">标签</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>1</td>
          <td>梨皮朱泥三绝清代小品壶经典款紫砂壶</td>
          <td>
            <img src="https://yanxuan-item.nosdn.127.net/f8c37ffa41ab1eb84bff499e1f6acfc7.jpg" />
          </td>
          <td>
            <!-- 标签组件 -->
            <!-- 1.1 剪切标签组件和相关样式到外部子组件中 -->
            <!-- 1.5 使用子组件 -->
              <MyTag v-model="tempText"></MyTag>
          </td>
        </tr>
        <tr>
          <td>1</td>
          <td>梨皮朱泥三绝清代小品壶经典款紫砂壶</td>
          <td>
            <img src="https://yanxuan-item.nosdn.127.net/221317c85274a188174352474b859d7b.jpg" />
          </td>
          <td>
            <!-- 标签组件 -->
            <!-- 组件复用 -->
            <MyTag v-model="tempText2"></MyTag>
          </td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

<script>
// my-tag 标签组件的封装
  // 1.创建组件 - 初始化
  // 2. 实现功能
    // (1) 双击显示输入框,输入框获取焦点
            // v-if v-else @dbclick
            // 自动聚焦(2种方法)
            // 1. $neckTick => $refs 获取到dom,进行focus获取焦点
            // 2. 封装v-focus指令
    // (2) 失去焦点,隐藏输入框
            // @blur 操作 isEdit
    // (3) 回显标签信息
            // 回显的标签信息是父组件传递过来的
            // v-model实现功能(简化代码) v-model => :value 和 @input
    // (4) 内容修改,回车 → 修改标签信息
            // @keyup.enter,触发事件 $emit('input',e.target.value)

// 1.3 导入子组件
import MyTag from './components/MyTag.vue'
export default {
  name: 'TableCase',
  components: {
    // 1.4 注册局部组件
    MyTag
  },
  data() {
    return {
      // 测试的数据
      tempText:'水杯',
      tempText2:'钢笔',
      goods: [
        {
          id: 101,
          picture:
            'https://yanxuan-item.nosdn.127.net/f8c37ffa41ab1eb84bff499e1f6acfc7.jpg',
          name: '梨皮朱泥三绝清代小品壶经典款紫砂壶',
          tag: '茶具',
        },
        {
          id: 102,
          picture:
            'https://yanxuan-item.nosdn.127.net/221317c85274a188174352474b859d7b.jpg',
          name: '全防水HABU旋钮牛皮户外徒步鞋山宁泰抗菌',
          tag: '男鞋',
        },
        {
          id: 103,
          picture:
            'https://yanxuan-item.nosdn.127.net/cd4b840751ef4f7505c85004f0bebcb5.png',
          name: '毛茸茸小熊出没,儿童羊羔绒背心73-90cm',
          tag: '儿童服饰',
        },
        {
          id: 104,
          picture:
            'https://yanxuan-item.nosdn.127.net/56eb25a38d7a630e76a608a9360eec6b.jpg',
          name: '基础百搭,儿童套头针织毛衣1-9岁',
          tag: '儿童服饰',
        },
      ],
    }
  },
}
</script>

<style lang="less" scoped>
.table-case {
  width: 1000px;
  margin: 50px auto;
  img {
    width: 100px;
    height: 100px;
    object-fit: contain;
    vertical-align: middle;
  }

  .my-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 0.5s;
      &.red {
        color: red;
      }
    }
    .none {
      height: 100px;
      line-height: 100px;
      color: #999;
    }
  }
  
}
</style>
Main.js

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

Vue.config.productionTip = false
// 把这个获取焦点封装成全局指令 v-focus
Vue.directive('focus',{
  // 指令所在的dom元素,被插入到页面中时触发
  inserted(el){
    el.focus()
  }
})

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


MyTag.vue

<template>
  <!-- 1.2 从父组件那拿到小组件和样式 粘贴在子组件中 -->
  <!-- 创建商品信息组件 -->

  <!-- 2.1 控制显示隐藏 v-if,v-else  -->
  <!-- v-focus获取焦点 -->
  <!-- 注册失去焦点事件,写行内 -->
  <!-- :value="value" 进行回显 -->
  <div class="my-tag">
    <input ref="inp" 
    :value="value" 
    v-focus 
    v-if="isEdit" 
    class="input" 
    type="text" 
    placeholder="输入标签" @blur="isEdit=false"
    @keyup.enter="handleEnter"
    />
    <!-- 2.1 双击text 控制显示隐藏 -->
    <div v-else @dblclick="handleClick" 
    class="text">{{ value }}</div>
  </div>
</template>

<script>
export default {
    props:{
       value:String 
    },
  data() {
    return {
    // 2.1 显示隐藏的布尔值
      isEdit:false
    }
  },
  methods: {
    // 2.1双击后,切换到显示状态(Vue是异步dom更新)
    handleClick() {
      this.isEdit = true;

    // 可以把这个获取焦点封装成全局指令 v-focus
    // // 等dom更新完了,再获取焦点
    // this.$nextTick(() => {
    //     // 立刻获取焦点
    //     this.$refs.inp.focus()
    // })
    
    },
    handleEnter(e){
        // 非空处理
        if(e.target.value.trim() === '') return alert('标签内容不能为空')
        // 子传父,将回车时,[输入框的内容]提交给父组件更新
        // 由于父组件是v-model,触发事件,需要触发input事件
        this.$emit('input',e.target.value)
        // 提交完成,关闭输入状态
        this.isEdit = false
    }
  },
};
</script>

// lang='less' 表示该组件的样式使用的是 LESS 语言。LESS 是一种 CSS 预处理器,它可以帮助我们更方便、更快速地编写 CSS 样式。
// scoped 则表示该样式仅在当前组件内生效
<style lang='less' scoped>
.my-tag {
  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;
    }
  }
}
</style>

(2)表格封装-插槽

image.png

image.png

5.4 路由入门

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png