前端学习——Vue (Day2)

指令补充

指令修饰符

在这里插入图片描述
在这里插入图片描述

<!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>@keyup.enter  →  监听键盘回车事件</h3>
    <input @keyup="fn" v-model="username" type="text">
  </div>
  <script src="./vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        username: ''
      },
      methods: {
        fn(e){
          if(e.key === 'Enter'){
            console.log('键盘回车的时候触发',this.username);
          }
        }
      }
    })
  </script>
</body>
</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">
  <title>Document</title>
  <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>v-model修饰符 .trim .number</h3>
    姓名:<input v-model.trim="username" type="text"><br>
    年纪:<input v-model,number="age" type="text"><br>

    
    <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="./vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        username: '',
        age: '',
      },
      methods: {
        fatherFn () {
          alert('老父亲被点击了')
        },
        sonFn () {
          alert('儿子被点击了')
        }
      }
    })
  </script>
</body>
</html>

在这里插入图片描述

v-bind 对于样式控制的增强

在这里插入图片描述

操作class

在这里插入图片描述

<!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>
    .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="./vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {

      }
    })
  </script>
</body>
</html>

在这里插入图片描述

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

在这里插入图片描述

<!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;
    }
    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:index === activeIndex}" href="#">{{ item.name }}</a>
      </li>
    </ul>
  </div>
  <script src="./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>
</html>

在这里插入图片描述

操作style

在这里插入图片描述

<!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>
    .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',backgroundColor:'green'}"></div>
  </div>
  <script src="./vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {

      }
    })
  </script>
</body>

</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">
  <title>Document</title>
  <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="./vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        percent:0
      }
    })
  </script>
</body>
</html>

在这里插入图片描述

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

在这里插入图片描述

<!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>
    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 v-model="gender" type="radio" name="gender" value="1"><input v-model="gender" type="radio" name="gender" value="0"><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="./vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        username:'',
        isSingle:true,
        gender:'1',
        cityId:'102',
        desc:''
      }
    })
  </script>
</body>
</html>

在这里插入图片描述

computed 计算属性

在这里插入图片描述

<!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>
    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="./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() {
          // 基于现有数据,编写求值逻辑
          // 计算属性函数内部,可以直接通过this访问到app

          // 对this.list 数组里面num进行求和 -> reduce 
          let total = this.list.reduce((sum, item) => sum + item.num, 0)
          return total
        }
      }
    })
  </script>
</body>

</html>

在这里插入图片描述

computed 计算属性 vs methods 方法

在这里插入图片描述

计算属性完整写法

在这里插入图片描述

<!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 type="text" v-model="firstName"><br>
    名:<input type="text" v-model="lastName"><br>
    <span>{{ fullName }}</span>

    <button @click="changeName">修改姓名</button>
  </div>
  <script src="./vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        firstName:'林',
        lastName:'杨'
      },
      methods: {
        changeName(){
          this.fullName='余周周'
        }
      },
      computed: {
        // 简写->获取
        // fullName(){
        //   return this.firstName+this.lastName
        // }

        // 完整写法 -> 获取 + 设置
        fullName:{
          get(){
            return this.firstName+this.lastName
          },
          // 当fullName计算属性,被修改赋值时,执行set,修改的值,传递给set方法的形参
          set(value){
            this.firstName = value.slice(0,1)
            this.lastName = value.slice(1)
          }
        }
      }
    })
  </script>
</body>
</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" />
  <title>Document</title>
  <style>
    .score-case {
      width: 1000px;
      margin: 50px auto;
      display: flex;
    }

    .score-case .table {
      flex: 4;
    }

    .score-case .table table {
      width: 100%;
      border-spacing: 0;
      border-top: 1px solid #ccc;
      border-left: 1px solid #ccc;
    }

    .score-case .table table th {
      background: #f5f5f5;
    }

    .score-case .table table tr:hover td {
      background: #f5f5f5;
    }

    .score-case .table table td,
    .score-case .table table th {
      border-bottom: 1px solid #ccc;
      border-right: 1px solid #ccc;
      text-align: center;
      padding: 10px;
    }

    .score-case .table table td.red,
    .score-case .table table th.red {
      color: red;
    }

    .score-case .table .none {
      height: 100px;
      line-height: 100px;
      color: #999;
    }

    .score-case .form {
      flex: 1;
      padding: 20px;
    }

    .score-case .form .form-item {
      display: flex;
      margin-bottom: 20px;
      align-items: center;
    }

    .score-case .form .form-item .label {
      width: 60px;
      text-align: right;
      font-size: 14px;
    }

    .score-case .form .form-item .input {
      flex: 1;
    }

    .score-case .form .form-item input,
    .score-case .form .form-item select {
      appearance: none;
      outline: none;
      border: 1px solid #ccc;
      width: 200px;
      height: 40px;
      box-sizing: border-box;
      padding: 10px;
      color: #666;
    }

    .score-case .form .form-item input::placeholder {
      color: #666;
    }

    .score-case .form .form-item .cancel,
    .score-case .form .form-item .submit {
      appearance: none;
      outline: none;
      border: 1px solid #ccc;
      border-radius: 4px;
      padding: 4px 10px;
      margin-right: 10px;
      font-size: 12px;
      background: #ccc;
    }

    .score-case .form .form-item .submit {
      border-color: #069;
      background: #069;
      color: #fff;
    }
  </style>
</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">
            <td>{{ index + 1 }}</td>
            <td>{{ item.subject }}</td>
            <!-- 成绩不及格标红 -->
            <td :class="{'red':item.score<60}">{{ item.score }}</td>
            <td><a @click.prevent="del(item.id)" href="#">删除</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">平均分:{{ avg }}</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="../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: ''
      },
      computed: {
        totalScore() {
          return this.list.reduce((sum, item) => sum + item.score, 0)
        },
        avg(){
          if(this.list.length ===0){
            return 0
          }
          return (this.totalScore/this.list.length).toFixed(2)
        }
      },
      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.subject = ''
          this.score = ''
        }
      }
    })
  </script>
</body>

</html>

在这里插入图片描述
在这里插入图片描述

watch 侦听器(监视器)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
简写

<!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>
          <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: ''
          },
          result: '', // 翻译结果
          // timer: null // 延时器id
        },
        // 具体讲解:(1) watch语法 (2) 具体业务实现
        watch: {
          // 该方法会在数据变化时调用执行
          // newValue新值, oldValue老值(一般不用)
          // words (newValue) {
          //   console.log('变化了', newValue)
          // }

          'obj.words' (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
                }
              })
              this.result = res.data.data
              console.log(res.data.data)
            }, 300)
          }
        }
      })
    </script>
  </body>
</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" />
    <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>

在这里插入图片描述

综合案例 - 水果购物车

在这里插入图片描述

<!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>购物车</title>
  <style>
    .app-container {
      padding-bottom: 300px;
      width: 800px;
      margin: 0 auto;
    }

    @media screen and (max-width: 800px) {
      .app-container {
        width: 600px;
      }
    }

    .app-container .banner-box {
      border-radius: 20px;
      overflow: hidden;
      margin-bottom: 10px;
    }

    .app-container .banner-box img {
      width: 100%;
    }

    .app-container .nav-box {
      background: #ddedec;
      height: 60px;
      border-radius: 10px;
      padding-left: 20px;
      display: flex;
      align-items: center;
    }

    .app-container .nav-box .my-nav {
      display: inline-block;
      background: #5fca71;
      border-radius: 5px;
      width: 90px;
      height: 35px;
      color: white;
      text-align: center;
      line-height: 35px;
      margin-right: 10px;
    }

    .breadcrumb {
      font-size: 16px;
      color: gray;
    }

    .table {
      width: 100%;
      text-align: left;
      border-radius: 2px 2px 0 0;
      border-collapse: separate;
      border-spacing: 0;
    }

    .th {
      color: rgba(0, 0, 0, 0.85);
      font-weight: 500;
      text-align: left;
      background: #fafafa;
      border-bottom: 1px solid #f0f0f0;
      transition: background 0.3s ease;
    }

    .th.num-th {
      flex: 1.5;
    }

    .th {
      text-align: center;
    }

    .th:nth-child(4),
    .th:nth-child(5),
    .th:nth-child(6),
    .th:nth-child(7) {
      text-align: center;
    }

    .th.th-pic {
      flex: 1.3;
    }

    .th:nth-child(6) {
      flex: 1.3;
    }

    .th,
    .td {
      position: relative;
      padding: 16px 16px;
      overflow-wrap: break-word;
      flex: 1;
    }

    .pick-td {
      font-size: 14px;
    }

    .main,
    .empty {
      border: 1px solid #f0f0f0;
      margin-top: 10px;
    }

    .tr {
      display: flex;
      cursor: pointer;
      border-bottom: 1px solid #ebeef5;
    }

    .tr.active {
      background-color: #f5f7fa;
    }

    .td {
      display: flex;
      justify-content: center;
      align-items: center;
    }

    .table img {
      width: 100px;
      height: 100px;
    }

    button {
      outline: 0;
      box-shadow: none;
      color: #fff;
      background: #d9363e;
      border-color: #d9363e;
      color: #fff;
      background: #d9363e;
      border-color: #d9363e;
      line-height: 1.5715;
      position: relative;
      display: inline-block;
      font-weight: 400;
      white-space: nowrap;
      text-align: center;
      background-image: none;
      border: 1px solid transparent;
      box-shadow: 0 2px 0 rgb(0 0 0 / 2%);
      cursor: pointer;
      transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
      -webkit-user-select: none;
      -moz-user-select: none;
      -ms-user-select: none;
      user-select: none;
      touch-action: manipulation;
      height: 32px;
      padding: 4px 15px;
      font-size: 14px;
      border-radius: 2px;
    }

    button.pay {
      background-color: #3f85ed;
      margin-left: 20px;
    }

    .bottom {
      height: 60px;
      display: flex;
      align-items: center;
      justify-content: space-between;
      padding-right: 20px;
      border: 1px solid #f0f0f0;
      border-top: none;
      padding-left: 20px;
    }

    .right-box {
      display: flex;
      align-items: center;
    }

    .check-all {
      cursor: pointer;
    }

    .price {
      color: hotpink;
      font-size: 30px;
      font-weight: 700;
    }

    .price-box {
      display: flex;
      align-items: center;
    }

    .empty {
      padding: 20px;
      text-align: center;
      font-size: 30px;
      color: #909399;
    }

    .my-input-number {
      display: flex;
    }

    .my-input-number button {
      height: 40px;
      color: #333;
      border: 1px solid #dcdfe6;
      background-color: #f5f7fa;
    }

    .my-input-number button:disabled {
      cursor: not-allowed !important;
    }

    .my-input-number .my-input__inner {
      height: 40px;
      width: 50px;
      padding: 0;
      border: none;
      border-top: 1px solid #dcdfe6;
      border-bottom: 1px solid #dcdfe6;
    }

    .my-input-number {
      position: relative;
      display: inline-block;
      width: 140px;
      line-height: 38px;
    }

    .my-input-number span {
      -moz-user-select: none;
      -webkit-user-select: none;
      -ms-user-select: none;
    }

    .my-input-number .my-input {
      display: block;
      position: relative;
      font-size: 14px;
      width: 100%;
    }

    .my-input-number .my-input__inner {
      -webkit-appearance: none;
      background-color: #fff;
      background-image: none;
      border-radius: 4px;
      border: 1px solid #dcdfe6;
      box-sizing: border-box;
      color: #606266;
      display: inline-block;
      font-size: inherit;
      height: 40px;
      line-height: 40px;
      outline: none;
      padding: 0 15px;
      transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
      width: 100%;
      padding-left: 50px;
      padding-right: 50px;
      text-align: center;
    }

    .my-input-number .my-input-number__decrease,
    .my-input-number .my-input-number__increase {
      position: absolute;
      z-index: 1;
      top: 1px;
      width: 40px;
      height: auto;
      text-align: center;
      background: #f5f7fa;
      color: #606266;
      cursor: pointer;
      font-size: 13px;
    }

    .my-input-number .my-input-number__decrease {
      left: 1px;
      border-radius: 4px 0 0 4px;
      border-right: 1px solid #dcdfe6;
    }

    .my-input-number .my-input-number__increase {
      right: 1px;
      border-radius: 0 4px 4px 0;
      border-left: 1px solid #dcdfe6;
    }

    .my-input-number .my-input-number__decrease.is-disabled,
    .my-input-number .my-input-number__increase.is-disabled {
      color: #c0c4cc;
      cursor: not-allowed;
    }
  </style>
</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">
          <div v-for="(item,index) in fruitList" :key="item.id" class="tr" :class="{active:item.isChecked}">
            <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 :disabled="item.num<=1" class="decrease" @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: {
        // 水果列表
        fruitList: JSON.parse(localStorage.getItem('list')) || defaultArr,
      },
      computed:{
        // 默认计算属性:只能获取不能设置,要设置需要写完整写法
        // isAll(){
        //   // 必须所有小选框都选中,全选按钮才选中 ->every
        //   return this.fruitList.every(item=>item.isChecked)
        // }

        isAll:{
          get(){
            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)=>{
            if(item.isChecked){
              return sum+item.num*item.price
            }
            else{
              return sum
            }
          },0)
        }
      },
      methods: {
        del(id) {
          this.fruitList = this.fruitList.filter(item => item.id !== id)
        },
        sub(id){
          // 1. 根据 id 找到数组中的对应项 -> find
          const fruit = this.fruitList.find(item => item.id===id)
          // 2. 操作num数量
          fruit.num--
        },
        add(id){
          // 1. 根据 id 找到数组中的对应项 -> find
          const fruit = this.fruitList.find(item => item.id===id)
          // 2. 操作num数量
          fruit.num++
        }
      },
      watch:{
        fruitList:{
          deep:true,
          handler(newValue){
            // 需要将变化后的 newValue 存入本地(转JSON)
            localStorage.setItem('list',JSON.stringify(newValue))
          }
        }
      }
    })
  </script>
</body>

</html>

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/43118.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

两天学会用Webpack打包前端代码-day01

(创作不易&#xff0c;感谢有你&#xff0c;你的支持&#xff0c;就是我前行的最大动力&#xff0c;如果看完对你有帮助&#xff0c;请留下您的足迹&#xff09; 目录 什么是 Webpack&#xff1f; 使用 Webpack 体验webpack打包过程 修改 Webpack 打包入口和出口 入口 出…

vue3+taro+Nutui 开发小程序(一)

前言&#xff1a;最近在调研开发小程序&#xff0c;发现现在taro框架逐渐成熟&#xff0c;能完美地使用vue3来进行开发&#xff0c;调研中发现京东的Nutui也不错所以准备写一个由0到1的vue3taroNutui的小程序。 这篇我们首先搭建一个框架&#xff1a; vscode插件准备环节&…

Sublime Text 4 激活教程(Windows+Mac)

下载安装 官网 https://www.sublimetext.com 点击跳转 2023.7.21 版本为4143 Windows激活方式 一、激活License方式 入口在菜单栏中"Help” -> “Enter License” 注意格式&#xff0c;可能会过期失效&#xff0c;失效就用方式二 Mifeng User Single User License E…

F.interpolate 数组采样操作

功能&#xff1a;利用插值方法&#xff0c;对输入的张量数组进行上\下采样操作&#xff0c;换句话说就是科学合理地改变数组的尺寸大小&#xff0c;尽量保持数据完整。 在计算机视觉中&#xff0c;interpolate函数常用于图像的放大(即上采样操作)。比如在细粒度识别领域中&…

《Unix环境高级编程》第三版源代码编译

CentOS 7.6 进行编译 使用cat /etc/redhat-release看到操作系统是CentOS Linux release 7.6.1810 (Core)&#xff0c;使用uname -r看到内核是3.10.0-957.el7.x86_64&#xff0c;gcc --version可以看到gcc版本是4.8.5。 wget http://www.apuebook.com/src.3e.tar.gz下载《Uni…

java学习路程之篇一、进阶知识、面向对象高级、static关键字、继承、final关键字、this、super

文章目录 1、面向对象高级2、static关键字3、继承4、final关键字 1、面向对象高级 2、static关键字 3、继承 4、final关键字

MyBatis学习笔记——4

MyBatis学习笔记——4 一、MyBatis的高级映射及延迟加载1.1、多对一1.1.1、第一种方式&#xff1a;级联属性映射1.1.2、第二种方式&#xff1a;association1.1.3、第三种方式&#xff1a;分步查询 1.2、一对多1.2.1、第一种方式&#xff1a;collection1.2.1、第二种方式&#x…

【OAuth2】OAuth2概述及使用GitHub登录第三方网站

【OAuth2】OAuth2概述及使用GitHub登录第三方网站 文章目录 【OAuth2】OAuth2概述及使用GitHub登录第三方网站0. 导言1. OAuth2 简介2. OAuth2 认证授权总体流程3. OAuth2 标准接口4. OAuth2 四种授权模式4.1 授权码模式4.2 简化模式4.3 密码模式4.4 客户端模式 5. GitHub授权登…

Docker 的数据管理、容器互联、镜像创建

目录 一、数据管理 1.数据卷 2. 数据卷容器 二、容器互联&#xff08;使用centos镜像&#xff09; 三、Docker 镜像的创建 1.基于现有镜像创建 1.1首先启动一个镜像&#xff0c;在容器里修改 1.2将修改后的容器提交为新的镜像&#xff0c;需使用该容器的id号创建新镜像 …

电脑显示连接上WiFi,但没办法上网

问题: 电脑显示已经连接上WiFi。但是百度不出来东西&#xff0c;也没办法打开任何网页。 解决方法&#xff1a; win10系统 在左下角搜索栏&#xff0c;搜索“代理服务器设置”。 找到手动设置代理 —》关闭“使用代理服务” 【默认是打开的】 关闭之后即可上网~~

【Git】分支合并冲突产生与解决

文章学习自&#xff1a;麦兜搞IT&#xff0c;如有侵权&#xff0c;告知删除 文章目录 前言1 Fast Forword 合并1.1 核心原理1.2 举个栗子1.3 经验之谈 2 three way merge2.1 核心原理2.2 举个栗子&#xff08;不带冲突&#xff09;2.3 带冲突的three way merge 3 变基rebase3.…

Android WiFi框架概览

概览 Android 提供默认 Android 框架实现&#xff0c;其中包括对各种 WLAN 协议和模式的支持&#xff0c;这些协议和模式包括&#xff1a; WLAN 基础架构 (STA)网络共享模式或仅限本地模式下的 WLAN 热点 (Soft AP)WLAN 直连&#xff08;点对点&#xff09;WLAN 感知 (NAN)WL…

【简单认识MySQL主从复制与读写分离】

文章目录 一、MySQL主从复制1、配置主从复制的原因&#xff1a;2、主从复制原理1、 MySQL的复制类型2、 MySQL主从复制的工作过程;1、 MySQL主从复制延迟2、优化方案&#xff1a;3、 MySQL 有几种同步方式&#xff1a; 三种4、异步复制&#xff08;Async Replication&#xff0…

Stream流List转Map报错Duplicate key StreamMap

项目场景&#xff1a; JDK8引入了Stream流&#xff0c;让程序员在开发中更方便进行集合之间的转换&#xff0c;在使用Stream流将List转为Map时&#xff0c;如果Map的key有重复的情况下&#xff0c;就会抛出java.lang.IllegalStateException: Duplicate key StreamMap这个异常。…

算法竞赛备赛之经典数据结构训练提升,暑期集训营培训

1.链表与邻接表&#xff1a;树与图的存储 我们将结构体和指针结合来实现链表 struct Node {int val;Node * next; }; ​ new Node;//这样创建结点是相当慢的 我们算法主要是用数组来模拟链表&#xff0c;这样效率会高一些。 数组模拟单链表 邻接表&#xff1a;存储图和树 实…

KubeVela篇06:Kubevela Addon插件安装原理

addon支持从本地、git仓库、helm chart仓库安装,最终原理都相同,因此我们以本地安装为例。 完整流程如下: 从指定目录读取一个完整的addon安装包。 根据metadata.yaml配置文件,校验插件要求的kubevela、k8s的版本,不满足版本要求则终止安装。 根据metadata.yaml配置文件…

深入理解 PostgreSQL 的架构和内部工作原理

&#x1f337;&#x1f341; 博主 libin9iOak带您 Go to New World.✨&#x1f341; &#x1f984; 个人主页——libin9iOak的博客&#x1f390; &#x1f433; 《面试题大全》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33…

FPGA配置文件从串并模式下载

FPGA配置文件的下载模式有5种&#xff1a; 主串模式&#xff08;master serial&#xff09;从串模式&#xff08;slave serial&#xff09;主并模式&#xff08;master selectMAP&#xff09;从并模式&#xff08;slave selectMAP&#xff09;JTAG模式 其中&#xff0c;JTAG模…

信捷PLC RC低通滤波器(C语言实现)

PLC信号处理系列之RC低通滤波器算法详细介绍请参考下面文章: PLC信号处理系列之一阶低通(RC)滤波器算法_plc滤波算法程序_RXXW_Dor的博客-CSDN博客1、先看看RC滤波的优缺点 优点:采用数字滤波算法来实现动态的RC滤波,则能很好的克服模拟滤波器的缺点; 1、在模拟常数要求较…

什么小程序需要商家自营相关类目?

1、百货&#xff1a;小程序主体公司综合零售商&#xff0c;在线售卖多种日用品&#xff0c;需补充商家自营-百货类目。预包装食品定义&#xff1a; 预包装食品&#xff0c;指预先定量包装或者制作在包装材料和容器中的食品&#xff1b;包括预先定量包装以及预先定量制作在包装…