当前位置: 首页 > article >正文

Vue笔记(二)

一、指令的修饰符

1.指令修饰符通过“.”指明指令后缀,不同后缀封装特定处理操作,简化代码编写。常见的指令修饰符包括:按键修饰符、v-model修饰符、事件修饰符

(一)按键修饰符

@keyup.enter 用于监听键盘回车键的按下操作,方便对回车事件进行响应处理。

(二) v-model修饰符

 v-model.trim 可去除表单输入内容的首尾空格; v-model.number 能将用户输入值转为数字类型,常用于需要进行数值计算的场景。

(三) 事件修饰符

 @事件名.stop 用于阻止事件冒泡,防止事件向上级元素传递; @事件名.prevent 用于阻止元素的默认行为,例如阻止链接的默认跳转行为。

总结

二、v-bind对于样式控制的增强-操作class

1.使用 v-bind 指令对元素的 class 进行操作,语法为 :class="对象/数组" 。

(一)对象语法

键为类名,值为布尔值。当值为 true 时,元素添加该类名;值为 false 时,则不添加。适用于单个类名的切换场景,

如 <div class="box" :class="{ 类名1:布尔值, 类名2:布尔值 }"></div> 。

(二) 数组语法

数组中的所有类名都会添加到元素上,本质是生成一个 class 列表。适用于批量添加或删除类的场景,

如 <div class="box" :class="[ 类名1, 类名2, 类名3 ]"></div> ,也可直接写成 class="['pink','big']" 。

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

1. v-model 可用于常见表单元素,能快速获取或设置表单元素的值。它会根据控件类型自动选取正确的方法来更新元素:

输入框(input:text):通过 value 属性进行数据绑定。
 
文本域(textarea):同样使用 value 属性绑定数据。
 
复选框(input:checkbox):使用 checked 属性,用于判断复选框是否被选中。
 
单选框(input:radio):也是通过 checked 属性来确定是否被选中。
 
下拉菜单(select):利用 value 属性获取或设置选中的值。

 

四、计算属性

计算属性是基于现有数据计算得到的新属性,当依赖的数据发生变化时,会自动重新计算。

 语法:在 computed 配置项中声明,一个计算属性对应一个函数。在模板中使用时和普通属性一样,如 { {计算属性名}} 。

 示例:假设存在数据 data() { return { num1: 10, num2: 20 } } ,计算属性 computed: { sum() { return this.num1 + this.num2 } } ,在模板中 { {sum}} 即可显示计算结果30 。

 

五、computed计算属性vs methods方法

(一) computed计算属性

主要用于封装数据处理逻辑,求得一个结果。写在 computed 配置项中,作为属性直接使用,如 this.计算属性 或 { {计算属性}} 。具有缓存特性,计算结果会被缓存,当依赖项未变化时,再次使用直接读取缓存;依赖项变化时,会自动重新计算并再次缓存,提升性能。
 

(二)methods方法

为实例提供处理业务逻辑的方法,写在 methods 配置项中。使用时需要调用,如 this.方法名() 、 { {方法名()}} 或在事件绑定中使用 @事件名="方法名" 。方法不会缓存执行结果,每次调用都会重新执行方法内的代码。

总结

六、计算属性完整写法

计算属性默认简写形式只能读取访问,若要实现“修改”功能,需使用完整写法。

computed: {
  计算属性名: {
    get() {
      // 计算逻辑
      return 结果
    },
    set(修改的值) {
      // 修改逻辑
    }
  }
}

 

七、成绩案例

(一)index.less

.score-case {
  width: 1000px;
  margin: 50px auto;
  display: flex;
  .table {
    flex: 4;
    table {
      width: 100%;
      border-spacing: 0;
      border-top: 1px solid #ccc;
      border-left: 1px solid #ccc;
      th {
        background: #f5f5f5;
      }
      tr:hover td {
        background: #f5f5f5;
      }
      td,
      th {
        border-bottom: 1px solid #ccc;
        border-right: 1px solid #ccc;
        text-align: center;
        padding: 10px;
        &.red {
          color: red;
        }
      }
    }
    .none {
      height: 100px;
      line-height: 100px;
      color: #999;
    }
  }
  .form {
    flex: 1;
    padding: 20px;
    .form-item {
      display: flex;
      margin-bottom: 20px;
      align-items: center;
    }
    .form-item .label {
      width: 60px;
      text-align: right;
      font-size: 14px;
    }
    .form-item .input {
      flex: 1;
    }
    .form-item input,
    .form-item select {
      appearance: none;
      outline: none;
      border: 1px solid #ccc;
      width: 200px;
      height: 40px;
      box-sizing: border-box;
      padding: 10px;
      color: #666;
    }
    .form-item input::placeholder {
      color: #666;
    }
    .form-item .cancel,
    .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;
    }
    .form-item .submit {
      border-color: #069;
      background: #069;
      color: #fff;
    }
  }
}

(二)index.css

.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;
}

(三)静态模版

<!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>
            <tr>
              <td>1</td>
              <td>语文</td>
              <td class="red">46</td>
              <td><a href="#">删除</a></td>
            </tr>
            <tr>
              <td>2</td>
              <td>英语</td>
              <td>80</td>
              <td><a href="#">删除</a></td>
            </tr>
            <tr>
              <td>3</td>
              <td>数学</td>
              <td>100</td>
              <td><a href="#">删除</a></td>
            </tr>
          </tbody>
          <tbody>
            <tr>
              <td colspan="5">
                <span class="none">暂无数据</span>
              </td>
            </tr>
          </tbody>
 
          <tfoot>
            <tr>
              <td colspan="5">
                <span>总分:246</span>
                <span style="margin-left: 50px">平均分:79</span>
              </td>
            </tr>
          </tfoot>
        </table>
      </div>
      <div class="form">
        <div class="form-item">
          <div class="label">科目:</div>
          <div class="input">
            <input
              type="text"
              placeholder="请输入科目"
            />
          </div>
        </div>
        <div class="form-item">
          <div class="label">分数:</div>
          <div class="input">
            <input
              type="text"
              placeholder="请输入分数"
            />
          </div>
        </div>
        <div class="form-item">
          <div class="label"></div>
          <div class="input">
            <button 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: 20 },
            { id: 7, subject: '数学', score: 99 },
            { id: 12, subject: '英语', score: 70 },
          ],
          subject: '',
          score: ''
        }
      })
    </script>
  </body>
</html>

八、watch侦听器(监视器)

1.作用:监视数据变化,执行相应的业务逻辑或异步操作。


 
2.语法:
 
① 简单写法:适用于监视简单类型数据的变化。当数据变化时,触发相应的回调函数,如:

data: {
  words: '苹果',
  obj: {
    words: '苹果'
  }
},
watch: {
  // 数据变化时执行
  数据属性名(newValue, oldValue) {
    // 业务逻辑或异步操作
  },
  '对象.属性名'(newValue, oldValue) {
    // 业务逻辑或异步操作
  }
}

② 完整写法:可添加额外配置项,如深度监视复杂类型数据和初始化时立刻执行回调函数。 deep: true 用于深度监视复杂类型数据的变化; immediate: true 使初始化时立刻执行一次 handler 方法,示例如下:

data: {
  obj: {
    words: '苹果',
    lang: 'italy'
  }
},
watch: {
  数据属性名: {
    deep: true,
    handler(newValue) {
      console.log(newValue)
    }
  }
}

 

九、水果购物车

(一)index.css

.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;

(二)inputnumber.css

.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;
}

(三)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="Fruit"></div>
      <!-- 面包屑 -->
      <div class="breadcrumb">
        <span>🏠</span>
        /
        <span>购物车</span>
      </div>
      <!-- 购物车主体 -->
      <!-- v-if v-else 条件渲染,如果没有数据则显示空车 -->
      <div v-if="fruitList.length > 0" class="main">
        <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>
          <!-- 身体 -->
          <!-- v-for 循环遍历渲染数组 -->
          <div v-for="(item, index) in fruitList" :key="item.id" class="tbody">
            <!-- v-bind 绑定 class 设置条件-->
            <div class="tr" :class="{ active: item.isChecked }">
              <!-- v-model 绑定属性 -->
              <div class="td"><input type="checkbox" v-model="item.isChecked" /></div>
              <!-- v-bind 绑定 src -->
              <div class="td"><img :src="item.icon" alt="" /></div>
              <div class="td">{
  
  { item.price }}</div>
              <div class="td">
                <div class="my-input-number">
                  <!-- @click 点击事件实现数量加减 -->
                  <!-- :disabled 是一个动态属性绑定,它允许你根据表达式的结果动态地设置 HTML 元素的 disabled 属性 -->
                  <button :disabled="item.num <= 1" class="decrease" @click="item.num--"> - </button>
                  <span class="my-input__inner">{
  
  { item.num }}</span>
                  <button class="increase" @click="item.num++"> + </button>
                </div>
              </div>
              <div class="td">{
  
  { item.price * item.num }}</div>
              <div @click="del(item.id)" class="td"><button>删除</button></div>
            </div>
          </div>
        </div>
        <!-- 底部 -->
        <div class="bottom">
          <!-- 全选 -->
          <label class="check-all">
            <!-- v-model 绑定计算属性实现全选功能 -->
            <input type="checkbox" v-model="isAll" @click=""/>
            全选
          </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 v-else class="empty">🛒空空如也</div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    <script>
      // 初始页面给出默认数组
      const defultArr = [
        {
          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: {
          // localStorage.getItem 是 Web 存储 API 的一部分,用于从本地存储中获取键名为 'list' 的数据项
          // JSON.parse 函数用于将 JSON 格式的字符串转换为 JavaScript 对象。如果从 localStorage 获取的字符串是有效的 JSON 字符串,它将被转换为对应的 JavaScript 对象
          // || 是逻辑或运算符。如果 JSON.parse(localStorage.getItem('list')) 的结果为 null 或 undefined(即本地存储中没有 'list' 键或其值为 null),则使用 defaultArr 作为默认值
          fruitList: JSON.parse(localStorage.getItem('list')) || defultArr ,
        },
        computed: {
          // 默认计算属性只能获取不能设置
          // 通过 every 检查每个水果的选中状态,如果都选中则全选按钮选中
          // isAll () {
          //   return this.fruitList.every(item => item.isChecked === true)
          // }
 
          // 完整写法 get + set
          isAll: {
            // get 用于获取
            get () {
              return this.fruitList.every(item => item.isChecked === true)
            },
            // set 用于设置
            set (value) {
              // 点击按钮时先判断
              // some() 方法实现如果有任意一个按钮不是 true,则全选,反之取消所有选择
              if (this.fruitList.some(item => item.isChecked !== true)) {
                this.fruitList.forEach(item => item.isChecked = true);
              } else {
                this.fruitList.forEach(item => item.isChecked = false);
              }
            }
          },
          totalCount () {
            return this.fruitList.reduce((sum, item) => {
              // 判断实现选中状态的才添加
              if (item.isChecked === true) {
                return sum + item.num
              } else {
                // 没选中则不干,返回 sum
                return sum
              }
            }, 0)
          },
          totalPrice () {
            return this.fruitList.reduce((sum, item) => {
              // 判断实现选中状态的才添加
              if (item.isChecked === true) {
                return sum + item.price * item.num
              } else {
                return sum
              }
            }, 0)
          }
        },
        methods: {
          del (id) {
            // filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素
            // 这是一个箭头函数,它接收数组中的每个元素作为参数(在这里是 item)。
            // item.id !== id:这是一个比较操作,检查 item 的 id 属性是否不等于传递给 filter 方法的 id 参数。如果不等于,返回 true;如果等于,返回 false
            this.fruitList = this.fruitList.filter(item => item.id !== id)
          },
        },
        // 使用 watch 监视数据保存到本地实现持久化
        // watch 是 Vue 实例的一个选项,用于定义一个或多个观察者,观察者可以观察 Vue 实例的数据变化
        watch: {
          fruitList: {
            deep: true,
            handler (newValue) {
              // 使用 $nextTick 确保在 DOM 更新完成后保存数据
              this.$nextTick(() => {
                // 需要将变化后的数据(newValue)存储本地(转 JSON)
                // localStorage.setItem('list', JSON.stringify(newValue)) 将 fruitList 数组的新值转换为 JSON 字符串,并存储到浏览器的本地存储中。
                // localStorage 是 Web 存储 API 的一部分,允许存储键值对数据,数据在页面会话结束时会被清除
                // JSON.stringify(newValue) 将 newValue(一个数组)转换为一个 JSON 格式的字符串,以便存储在本地存储中
                localStorage.setItem('list', JSON.stringify(newValue));
              });
            }
          }
        }
      })
    </script>
  </body>
</html>

 十、Vue生命周期和生命周期的四个阶段

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

2. 生命周期四个阶段:

创建阶段:在 new Vue() 后开始,进行响应式数据的初始化等操作。
 
挂载阶段:发送初始化渲染请求,将模板渲染成真实的DOM。
 
更新阶段:当数据发生修改时,更新视图以反映数据的变化。
 
销毁阶段:销毁Vue实例,释放相关资源。

 

3.Vue生命周期函数(钩子函数):在Vue生命周期的特定阶段会自动运行一些函数,开发者可在这些函数中编写自己的代码

有 beforeCreate 、 created 、 beforeMount 、 mounted 、 beforeUpdate 、 updated 、 beforeDestroy 、 destroyed ,其中 created 、 mounted 、 beforeDestroy 较为常用。

十一、小黑记账清单

<!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">
                    <input v-model.trim="name" type="text" class="form-control" placeholder="消费名称" />
                    <input v-model.number="price" type="text" class="form-control" placeholder="消费价格" />
                    <button @click="add" type="button" class="btn btn-primary">添加账单</button>
                </form>
 
                <table class="table table-hover">
                    <thead>
                        <tr>
                            <th>编号</th>
                            <th>消费名称</th>
                            <th>消费价格</th>
                            <th>操作</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr v-for="(item, index) in list" :key="item.id">
                            <td>{
  
  {index + 1}}</td>
                            <td>{
  
  {item.name}}</td>
                            <td :class="{ red: item.price > 500}">{
  
  {item.price.toFixed(2)}}</td>
                            <td><a @click="del(item.id)" href="javascript:;">删除</a></td>
                        </tr>
                    </tbody>
                    <tfoot>
                        <tr>
                            <td colspan="4">消费总计: {
  
  { totalPrice.toFixed(2) }}</td>
                        </tr>
                    </tfoot>
                </table>
            </div>
 
            <!-- 右侧图表 -->
            <div class="echarts-box" id="main"></div>
        </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/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)  mounted钩子实现
            *    (2) 根据数据实时更新饼图 echarts.setOption({ ... })
 
         */
        const app = new Vue({
            el: '#app',
            data: {
                list: [],
                name: '',
                price: ''
            },
            computed: {
                totalPrice() {
                    return this.list.reduce((sum, item) => sum + item.price, 0)
                }
            },
            created() {
                // const res = await axios.get('https://applet-base-api-t.itheima.net/bill',{
                //     params: {
                //         creator: '小黑'
                //     }
                // })
                // console.log(res);
                // this.list = res.data.data
 
                this.getList()
            },
            mounted() {
                this.myChart = echarts.init(document.querySelector('#main'))
                this.myChart.setOption({
                    // 大标题
                    title: {
                        text: '消费账单列表',
                        // subtext: 'Fake Data',
                        left: 'center'
                    },
                    // 提示框
                    tooltip: {
                        trigger: 'item'
                    },
                    // 图例
                    legend: {
                        orient: 'vertical',
                        left: 'left'
                    },
                    // 数据项
                    series: [
                        {
                            name: '消费账单',
                            type: 'pie',
                            radius: '50%',
                            data: [
                                //     { value: 400, name: '牙刷' },
                                //     { value: 299, name: '手表' },
                            ],
                            emphasis: {
                                itemStyle: {
                                    shadowBlur: 10,
                                    shadowOffsetX: 0,
                                    shadowColor: 'rgba(0, 0, 0, 0.5)'
                                }
                            }
                        }
                    ]
                })
            },
            methods: {
                async getList() {
                    const res = await axios.get('https://applet-base-api-t.itheima.net/bill', {
                        params: {
                            creator: '小黑'
                        }
                    })
                    console.log(res);
                    this.list = res.data.data
 
                    this.myChart.setOption({
                        series: [
                            {
                                // data: [
                                //     { value: 400, name: '牙刷' },
                                //     { value: 299, name: '手表' },
                                // ],
                                data: this.list.map(item => ({ value: item.price, name: item.name }))
                            }
                        ]
                    })
                },
 
                async add() {
                    if (!this.name) {
                        alert('请输入消费名称')
                        return
                    }
                    if (typeof this.price !== 'number') {
                        alert('请输入正确的消费价格')
                        return
                    }
                    const res = await axios.post('https://applet-base-api-t.itheima.net/bill', {
                        creator: '小黑',
                        name: this.name,
                        price: this.price
                    })
                    console.log(res)
                    this.getList()
 
                    this.name = ''
                    this.price = ''
                },
                async del(id) {
                    const res = await axios.delete(`https://applet-base-api-t.itheima.net/bill/${id}`)
                    // console.log(res);
                    this.getList()
                }
            }
        })
    </script>
</body>
 
</html>

 

十二、工程化开发和脚手架 

 

十三、组件化开发&根组件

1.组件化开发:将一个页面拆分成多个独立的组件,每个组件都有自己独立的结构、样式和行为。这种开发方式便于维护和复用,能有效提升开发效率。
 
2. 组件分类:分为普通组件和根组件。
 
 3.根组件:是整个应用最上层的组件,包裹所有普通小组件。以 App.vue 文件(单文件组件)为例,由三部分组成:

template:用于定义组件的结构,且必须有且只能有一个根元素。
 
script:编写组件的JavaScript逻辑代码。
 
style:设置组件的样式,支持 less 等预处理器,但需要安装 less 和 less-loader 相关依赖包。

 


http://www.kler.cn/a/536811.html

相关文章:

  • PHP PDO 教程
  • 换电脑了如何快速导出vscode里的插件
  • p5r预告信生成器API
  • matplotlib绘制三维曲面图时遇到的问题及解决方法
  • matlab simulink 汽车四分之一模型轮胎带阻尼
  • Verilog代码实例
  • 通过代理模式理解Java注解的实现原理
  • (2024|CVPR,MLLM 幻觉)OPERA:通过过度信任惩罚和回顾分配缓解多模态大型语言模型中的幻觉
  • 【深度学习】关于模型训练的一些基本概念
  • openai库 及LangChain 跟ChatGPT对话的主要接口
  • java虚拟机JVM简单介绍(可用于面试)
  • 微信小程序longpress以及touchend的bug,touchend不触发,touchend不执行
  • Java - 在Linux系统上使用OpenCV和Tesseract
  • Gitee AI上线:开启免费DeepSeek模型新时代
  • Nginx部署Umi React前端项目标准配置
  • 1.1 学习代理(Agent)分为几步?
  • 2025 CCF BDCI|“基于TPU平台的OCR模型性能优化”一等奖作品
  • Ubuntu 下 nginx-1.24.0 源码分析 - ngx_strerror 函数
  • 【LeetCode 刷题】贪心算法(4)-区间问题
  • Ubuntu Server 部署网页 (如果无域名 可局域网访问)
  • 【每天学点AI】实战仿射变换在人工智能图像处理中的应用
  • CTRL: 一种用于可控生成的条件Transformer语言模型
  • ROS2从入门到精通3-2:详解xacro语法并优化封装urdf
  • 如何在Docker中运行MySQL容器?
  • 电脑右下角小喇叭没反应怎么回事,快速解决方案
  • 【仿12306项目】通过纯前端+Kaptcha后端验证码,削弱瞬时高峰并防机器人刷票