Web前端-Vue2+Vue3基础入门到实战项目-Day2(指令补充, computed计算属性, watch侦听器, 水果购物车)

Web前端-Vue2+Vue3基础入门到实战项目-Day2

  • 指令补充
    • 指令修饰符
    • [v-bind 对样式控制的增强](#v-bind 对样式控制的增强)
      • 控制class
      • [案例 - 京东秒杀tab导航高亮](#案例 - 京东秒杀tab导航高亮)
      • 控制style
      • [案例 - 控制进度条](#案例 - 控制进度条)
    • [v-model 应用于其他表单元素](#v-model 应用于其他表单元素)
  • computed计算属性
  • watch侦听器
    • [简写 - 语法](#简写 - 语法)
    • [简写 - 业务实现](#简写 - 业务实现)
    • 完整写法
  • [综合案例 - 水果购物车](#综合案例 - 水果购物车)
  • 来源

指令补充

指令修饰符

  • @keyup.enter: 键盘回车监听
  • v-model.trim: 去除首尾空格
  • v-model.number: 转数字
  • @事件名.stop: 阻止冒泡
  • @事件名.prevent: 阻止默认行为
html 复制代码
<head>
  ...
  <style>
    .father {
      width: 200px;
      height: 200px;
      background-color: pink;
      margin-top: 20px;
    }
    .son {
      width: 100px;
      height: 100px;
      background-color: skyblue;
    }
  </style>
</head>
<body>
  <div id="app">
    <h3>@keyup.enter -> 监听键盘回车事件</h3>
    <input type="text" v-model="username" @keyup.enter="fn">

    <h3>v-model修饰符 .trim .number</h3>
    姓名: <input type="text" v-model.trim="username2"><br>
    年纪: <input type="text" v-model.number="age">

    <h3>@事件名.stop     →  阻止冒泡</h3>
    <div @click="fatherFn" class="father">
      <div @click.stop="sonFn" class="son">儿子</div>
    </div>

    <h3>@事件名.prevent  →  阻止默认行为</h3>
    <a @click.prevent href="http://www.baidu.com">阻止默认行为</a>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        username: '',
        username2: '',
        age: ''
      },
      methods: {
        fn(){
          console.log(this.username)
        },
        fatherFn () {
          alert('老父亲被点击了')
        },
        sonFn () {
          alert('儿子被点击了')
        }
      }
    })
  </script>
</body>

v-bind 对样式控制的增强

控制class

  • 对象使用场景: 一个类名, 来回切换
  • 数组使用场景: 批量添加或删除类
html 复制代码
<head>
  ...
  <style>
    .box {
      width: 200px;
      height: 200px;
      border: 3px solid #000;
      font-size: 30px;
      margin-top: 10px;
    }
    .pink {
      background-color: pink;
    }
    .big {
      width: 300px;
      height: 300px;
    }
  </style>
</head>
<body>

  <div id="app">
    <div class="box" :class="{pink: true, big: true}">黑马程序员</div>
    <div class="box" :class="['pink', 'big']">黑马程序员</div>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {

      }
    })
  </script>
</body>

案例 - 京东秒杀tab导航高亮

html 复制代码
<head>
  ...
  <style>
    * {
      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;
    }

  </style>
</head>
<body>

  <div id="app">
    <ul>
      <li v-for="(item, index) in list" :key="item.id" @click="activeIndex = index">
        <a :class="{active: activeIndex === index}" href="#"> {{item.name}} </a></li>
      </li>
    </ul>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        activeIndex: 0, // 记录高亮
        list: [
          { id: 1, name: '京东秒杀' },
          { id: 2, name: '每日特价' },
          { id: 3, name: '品类秒杀' }
        ]

      }
    })
  </script>
</body>

控制style

json对象的键不能有"-", 可以单引号引起来或者驼峰命名

html 复制代码
<head>
  ...
  <style>
    .box {
      width: 200px;
      height: 200px;
      background-color: rgb(187, 150, 156);
    }
  </style>
</head>
<body>
  <div id="app">
    <div class="box" :style="{width: '400px',  height: '400px', 'background-color': 'green'}"></div>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {

      }
    })
  </script>
</body>

案例 - 控制进度条

html 复制代码
<head>
  ...
  <style>
    .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;
    }
  </style>
</head>
<body>
  <div id="app">
    <!-- 外层盒子 - 底色(黑色) -->
    <div class="progress">
        <!-- 内层盒子 - 进度(蓝色) -->
      <div class="inner" :style="{width: percent+'%'}">
        <span> {{percent}}%</span>
      </div>
    </div>
    <button @click="percent = 25">设置25%</button>
    <button @click="percent = 50">设置50%</button>
    <button @click="percent = 75">设置75%</button>
    <button @click="percent = 100">设置100%</button>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        percent: 0
      }
    })
  </script>
</body>

v-model 应用于其他表单元素

v-model会根据控件类型自动选取正确的方法来更新元素

html 复制代码
<head>
  ...
  <style>
    textarea {
      display: block;
      width: 240px;
      height: 100px;
      margin: 10px 0;
    }
  </style>
</head>
<body>

  <div id="app">
    <h3>小黑学习网</h3>

    姓名:
      <input type="text" v-model="username"> 
      <br><br>

    是否单身:
      <input type="checkbox" v-model="isSingle"> 
      <br><br>

    <!-- 
      前置理解:
        1. name:  给单选框加上 name 属性 可以分组 → 同一组互相会互斥
        2. value: 给单选框加上 value 属性,用于提交给后台的数据
      结合 Vue 使用 → v-model
    -->
    性别: 
      <input type="radio" name="gender" value="1" v-model="gender">男
      <input type="radio" name="gender" value="2" v-model="gender">女
      <br><br>

    <!-- 
      前置理解:
        1. option 需要设置 value 值,提交给后台
        2. select 的 value 值,关联了选中的 option 的 value 值
      结合 Vue 使用 → v-model
    -->
    所在城市:
      <select v-model="cityId">
        <option value="101">上海</option>
        <option value="102">北京</option>
        <option value="103">成都</option>
        <option value="104">南京</option>
      </select>
      <br><br>

    自我描述:
      <textarea v-model="desc"></textarea> 

    <button>立即注册</button>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        username: '',
        isSingle: true,
        gender: '1',
        cityId: '101',
        desc: ''
      }
    })
  </script>
</body>

computed计算属性

基本使用

语法:

  • 声明在computed配置项中, 一个计算属性对应一个函数
  • 使用和普通属性一样使用
html 复制代码
<head>
  ...
  <style>
    table {
      border: 1px solid #000;
      text-align: center;
      width: 240px;
    }
    th,td {
      border: 1px solid #000;
    }
    h3 {
      position: relative;
    }
  </style>
</head>
<body>

  <div id="app">
    <h3>小黑的礼物清单</h3>
    <table>
      <tr>
        <th>名字</th>
        <th>数量</th>
      </tr>
      <tr v-for="(item, index) in list" :key="item.id">
        <td>{{ item.name }}</td>
        <td>{{ item.num }}个</td>
      </tr>
    </table>

    <!-- 目标:统计求和,求得礼物总数 -->
    <p>礼物总数:{{totalCount}} 个</p>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        // 现有的数据
        list: [
          { id: 1, name: '篮球', num: 1 },
          { id: 2, name: '玩具', num: 2 },
          { id: 3, name: '铅笔', num: 5 },
        ]
      },
      computed: {
        totalCount(){
          // reduce: 求和函数
          let total = this.list.reduce((sum, item) => sum+item.num, 0)
          return total
        }
      }
    })
  </script>
</body>

computed计算属性 vs methods方法

  • computed作用: 封装了一段对于数据的处理, 获得一个结果
  • methods作用: 给实例提供一个方法, 调用以处理业务逻辑
  • computed缓存特性(提升性能): 计算属性会对计算出来的结果缓存, 再次使用直接读取缓存, 依赖项变化了, 会自动重新计算, 并再次缓存

计算属性完整写法

  • 当计算属性被修改赋值时, 执行set方法, 修改的值, 传递给set方法的形参
html 复制代码
<body>
  <div id="app">
    姓:<input type="text" v-model="firstName"> +
    名:<input type="text" v-model="lastName"> =
    <span> {{fullName}} </span><br><br>
    <button @click="changeName">改名卡</button>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        firstName: '',
        lastName: ''
      },
      computed: {
        fullName: {
          get(){
            return this.firstName+this.lastName
          },
          set(value){
            this.firstName = value.slice(0, 1)
            this.lastName = value.slice(1)
          }
        }
      },
      methods: {
        changeName(){
          this.fullName = '阿巴巴'
        }
      }
    })
  </script>
</body>

案例 - 成绩

  • 渲染功能(不及格高亮)
    • v-if v-else
    • v-for
    • v-bind:class
  • 删除功能
    • 点击传参
    • filter过滤覆盖原数组
    • .prevent阻止默认行为
  • 添加功能
    • v-model v-model修饰符(.trim .number)
    • unshift修改数组更新视图
  • 统计总分, 求平均分
    • 计算属性
    • reduce秋娥和
html 复制代码
<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">
            <td> {{index+1}} </td>
            <td> {{item.subject}} </td>
            <td :class="{red: item.score < 60}"> {{item.score}} </td>
            <td><a href="#" @click.prevent="del(item.id)">删除</a></td>
          </tr>
        </tbody>
        <tbody v-else>
          <tr>
            <td colspan="5">
              <span class="none">暂无数据</span>
            </td>
          </tr>
        </tbody>

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

  <script>
    const app = new Vue({
      el: '#app',
      data: {
        list: [
          { id: 1, subject: '语文', score: 20 },
          { id: 7, subject: '数学', score: 99 },
          { id: 12, subject: '英语', score: 70 },
        ],
        subject: '',
        score: ''
      },
      methods: {
        del(id){
          this.list = this.list.filter(item => item.id!==id)
        },
        add(){
          if(!this.subject){
            alert('请输入科目')
            return
          }
          if(typeof this.score !== 'number'){
            alert('请输入正确的成绩')
            return
          }
          this.list.unshift({
            id: +new Date(),
            subject: this.subject,
            score: this.score
          })
          this.score = ''
          this.subject = ''
        }
      },
      computed: {
        scoreCount(){
          return this.list.reduce((sum, item) => sum+item.score, 0)
        },
        scoreAvg(){
          if(this.list.length === 0){
            return 0
          }
          return  (this.scoreCount/this.list.length).toFixed(2)
        }
      }
    })
  </script>
</body>

watch侦听器

简写 - 语法

js 复制代码
const app = new Vue({
  el: '#app',
  data: {
    // words: '',
    obj: {
      words: ''
    }
  },
  watch: {
    // words(newValue, oldValue){
    //   console.log(newValue, oldValue)
    // }

    'obj.words'(newValue, oldValue){
      console.log(newValue, oldValue)
    }
  }
})

简写 - 业务实现

html 复制代码
<head>
  ...
  <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>
        <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/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: ''
        },
        result: '', // 翻译结果

        // 1. 这个timer可以不写, 提高性能, 
        //    像this.timer这种写法可以挂载timer属性到当前实例上
        // 2. 非响应式的数据, 不渲染在页面上的数据都可以不写
        // timer: '' // 延迟期id
      },
      watch: {
        '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
            console.log(this.result)
          }, 300)
        }
      }
    })
  </script>
</body>

完整写法

  • deep: true: 对复杂类型深度剪视
  • immediate: true: 初始化执行一次handler方法
html 复制代码
<head>
  ...
  <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/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(newValue, oldValue){
            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(this.result)
            }, 300)
          }
        }
      }
    })
  </script>
</body>

综合案例 - 水果购物车

  • 渲染功能
    • v-if/v-else
    • v-for
    • :class
  • 删除功能
    • 点击传参
    • filter过滤覆盖原数组
  • 修改个数
    • 点击传参
    • find找对象
  • 全选反选
    • 计算属性computed
    • 计算属性完整写法 get/set
  • 统计选中的总价和总数量
    • 计算属性computed
    • reduce条件求和
  • 持久化到本地
    • watch监视
    • localStorage
    • JSON.stringify/JSON.parse
html 复制代码
<!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="./img/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">
            <div class="tr" :class="{active: item.isChecked}" v-for="(item, index) in fruitList" :key="item.id">
              <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">
                  <button class="decrease" @click="--item.num" :disabled="item.num <= 1"> - </button>
                  <span class="my-input__inner"> {{item.num}} </span>
                  <button class="increase" @click="add(item.id)"> + </button>
                </div>
              </div>
              <div class="td"> {{item.price*item.num}} </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="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
      const defaultArr = [
        {
          id: 1,
          icon: './img/火龙果.png',
          isChecked: true,
          num: 2,
          price: 6,
        },
        {
          id: 2,
          icon: './img/荔枝.png',
          isChecked: false,
          num: 7,
          price: 20,
        },
        {
          id: 3,
          icon: './img/榴莲.png',
          isChecked: false,
          num: 3,
          price: 40,
        },
        {
          id: 4,
          icon: './img/鸭梨.png',
          isChecked: true,
          num: 10,
          price: 3,
        },
        {
          id: 5,
          icon: './img/樱桃.png',
          isChecked: false,
          num: 20,
          price: 34,
        },
      ]
      const app = new Vue({
        el: '#app',
        data: {
          // 水果列表
          fruitList: JSON.parse(localStorage.getItem('list')) || defaultArr
        },
        methods: {
          del(id){
            this.fruitList = this.fruitList.filter(item => item.id !== id)
          },
          add(id){
            // 1. 根据id找到数组中的对应项 -> find
            const fruit = this.fruitList.find(item => item.id === id)
            // 2. 操作num数量
            ++fruit.num
          }
        },
        computed: {
          isAll: {
            get(){
              // 所有小选框都选中, 全选按钮才选中 -> every
              return this.fruitList.every(item => item.isChecked)
            },
            set(value){
              // 基于拿到的布尔值, 让所有的小选框同步状态
              this.fruitList.forEach(item => item.isChecked = value)
            }
          },
          totalCount(){
            return this.fruitList.reduce((sum, item) => {
              if(item.isChecked){
                return sum + item.num
              }else{
                return sum
              }
            }, 0)
          },
          totalPrice(){
            return this.fruitList.reduce((sum, item) => 
              item.isChecked ? sum+item.num*item.price:sum
            , 0)
          }
        },
        watch: {
          fruitList: {
            deep: true,
            handler(newValue){
              // 将变化后的newValue存入本地 (转JSON)
              localStorage.setItem('list', JSON.stringify(newValue))
            }
          }
        }
      })
    </script>
  </body>
</html>

来源

黑马程序员. Vue2+Vue3基础入门到实战项目

相关推荐
安晴晚风1 小时前
从0开始在linux服务器上部署SpringBoot和Vue
linux·运维·前端·数据库·后端·运维开发
前端小小王2 小时前
pnpm、Yarn 和 npm 的区别?
前端·npm·node.js
supermapsupport2 小时前
使用npm包的工程如何引入mapboxgl-enhance/maplibre-gl-enhance扩展包
前端·webpack·npm·supermap·mapboxgl
牛奔2 小时前
windows nvm 切换node版本后,npm找不到
前端·windows·npm·node.js
鱼大大博客2 小时前
Edge SCDN酷盾安全重塑高效安全内容分发新生态
前端·安全·edge
鸭梨山大。2 小时前
NPM组件包 vant部分版本内嵌挖矿代码
前端·安全·npm·node.js·vue
蟾宫曲7 小时前
在 Vue3 项目中实现计时器组件的使用(Vite+Vue3+Node+npm+Element-plus,附测试代码)
前端·npm·vue3·vite·element-plus·计时器
秋雨凉人心7 小时前
简单发布一个npm包
前端·javascript·webpack·npm·node.js
liuxin334455667 小时前
学籍管理系统:实现教育管理现代化
java·开发语言·前端·数据库·安全
qq13267029407 小时前
运行Zr.Admin项目(前端)
前端·vue2·zradmin前端·zradmin vue·运行zradmin·vue2版本zradmin