Vue笔记(二)

Vue(一):Vue笔记(一)-CSDN博客


目录

综合案例:水果购物车

 生命周期

1.生命周期&生命周期四个阶段

2.生命周期函数(钩子函数【8个】)

3.生命周期两个案例

初始化渲染

自动获取焦点

综合案例:小黑记账清单

工程化开发入门

1.工程化开发和脚手架

搭建教程

脚手架目录

注意点

更改镜像源

 node权限

修改服务器端口号

2.项目运行流程

main.js入口文件的作用

​编辑

3.组件化开发&根组件

App.vue的样式支持less

4.普通组件注册

组件注册的两种方式

局部注册

 案例演示

全局注册

案例演示

综合案例:小兔鲜首页

项目准备

组件注册

工程化进阶

组件的三大组成部分

组件的样式冲突scoped

scoped原理

​编辑

data必须是一个函数

组件通信

什么是组件通信

通信流程图

组件通信解决方案

父传子(props)

​编辑

子传父($emit)

非父子(event bus事件总线)

注意点

进阶语法

v-model原理

模版中获取形参

v-model处理表单类组件封装

简化代码

sync修饰符

ref 和 $refs获取组件dom

Vue异步更新、$nextTick


综合案例:水果购物车

项目功能:

视频链接:034-水果购物车-基本渲染_哔哩哔哩_bilibili

目录结构:

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

index.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" />
    
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <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">
        <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" v-for="(item, index) in fruitList" :key="item.id" :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="subtract(item.id)"> - </button>
                  <span class="my-input__inner">{{item.num}}</span>
                  <button class="increase" @click="addOne(item.id)"> + </button>
                </div>
              </div>
              <div class="td">{{ item.num * item.price}}</div>
              <div class="td"><button @click="delOne(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 v-show="fruitList.length === 0" class="empty">🛒空空如也</div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
    <script>
      const defaultList = [
            {
              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('fruit-list')) || defaultList,//一般给的初始值是[]空数组
        },
        methods:{
          // 删除一条
          delOne(id){
            this.fruitList = this.fruitList.filter(function(item){
              return item.id !== id;
            });
          },
          //个数增加1
          addOne(id){
            this.fruitList[id-1].num++;
          },
          //个数减少1
          subtract(id){
            this.fruitList[id-1].num--;
          }
        },
        computed:{
          // 默认计算属性:只能获取不能设置,要设置需要写完整写法
          // isAll () {
          //   // 必须所有的小选框都选中,全选按钮才选中 → every
          //   return this.fruitList.every(item => item.isChecked)
          // }
          
          // 完整写法 = get + set
          isAll: {
            get () {
              //使用Array.every()方法 检测所有小单选按钮是否都选中了,有一个没选中则返回false,全选不选中
              return this.fruitList.every(function(item){
                if(item.isChecked){
                  return true;
                };
                // 简写:item => item.isChecked;
              })
            },
            set (value) {
              // 基于拿到的布尔值,要让所有的小选框 同步状态
              this.fruitList.forEach(item => item.isChecked = value)
            }
          },
          // 计算选中数量
          totalCount(){
            return this.fruitList.reduce(function (x, y) {
              // 选中则累加
              if (y.isChecked === true){
                return x + y.num;
                // 否则返回上一次调用reduce的结果值
              }else{
                return x;
              }
            },0);
          },
          // 计算选中总价
          totalPrice(){
            return this.fruitList.reduce(function (x, y) {
              // 选中则累加
              if (y.isChecked === true){
                return x + y.price * y.num;
                // 否则返回上一次调用reduce的结果值
              }else{
                return x;
              }
            },0);
          }
        },
        watch:{
          // 对每次数据的改动做本地持久化,使用watch检测
          fruitList:{
            deep: true,
            handler(newValue,oldValue){
              // 需要将newValue存入本地,newValue是个复杂类型,转JSON格式存本地
              localStorage.setItem('fruit-list', JSON.stringify(newValue));
              // console.log(typeof newValue);// 类型:object
              // console.log(typeof localStorage.getItem('fruit-list'));// String JSON
              // console.log(typeof JSON.parse(localStorage.getItem('fruit-list')));// Object
              // console.log(typeof JSON.stringify(newValue));// String JSON 
            }
          }
        }
      })
    </script>
  </body>
</html>

 生命周期

1.生命周期&生命周期四个阶段

  1. 什么时候可以发送初始化渲染请求?(答:至少要等Vue实例创建出来,响应式数据准备好)
  2. 什么时候可以开始操作DOM?(答:至少DOM要渲染完成) 

2.生命周期函数(钩子函数【8个】)

共8个函数(4对,因为是成对出现)

  • beforeCreate() created()【成对出现】
  • beforeMount() mounted()
  • beforeUpdate() updated()
  • beforeDestroy() destroyed()

看到以下这个页面,就已经说明尽力了前两个阶段

①创建Vue实例,准备好了响应式数据

②渲染好了模板

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

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>

  <div id="app">
    <h3>{{ title }}</h3>
    <div>
      <button @click="count--">-</button>
      <span>{{ count }}</span>
      <button @click="count++">+</button>
    </div>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        count: 100,
        title: '计数器'
      },
      beforeCreate(){
        console.log('beforeCreate 响应式数据准备好之前');
      },
      created(){
        console.log('create 响应式数据准备好之后');
      },
      beforeMount(){
        console.log('beforeMount 模板渲染之前');
      },
      mounted(){
        console.log('mounted 模板渲染完成之后');
      }
    })
  </script>
</body>

</html>

3.生命周期两个案例

初始化渲染

功能要求:一进入页面先渲染如下,越早越好,写在created()中

JSON数据 

{
    "message": "获取新闻列表成功",
    "data": [
        {
            "id": 1,
            "title": "5G渗透率持续提升,创新业务快速成长",
            "source": "新京报经济新闻",
            "cmtcount": 58,
            "img": "http://ajax-api.itheima.net/images/0.webp",
            "time": "2222-10-28 11:50:28"
        },
        {
            "id": 5,
            "title": "为什么说中美阶段性协议再近一步,读懂周末的这些关键信息",
            "source": "澎湃新闻",
            "cmtcount": 131,
            "img": "http://ajax-api.itheima.net/images/4.webp",
            "time": "2222-10-24 09:08:34"
        },
        {
            "id": 6,
            "title": "阿根廷大选结果揭晓:反对派费尔南德斯有话要说",
            "source": "海外网",
            "cmtcount": 99,
            "img": "http://ajax-api.itheima.net/images/5.webp",
            "time": "2222-10-23 17:41:15"
        },
        {
            "id": 8,
            "title": "LV母公司当年史上最大并购:报价145亿美元购Tiffany",
            "source": "澎湃新闻",
            "cmtcount": 119,
            "img": "http://ajax-api.itheima.net/images/7.webp",
            "time": "2222-10-22 03:59:44"
        },
        {
            "id": 9,
            "title": "黄峥当年1350亿蝉联80后白手起家首富:1年中财富每天涨1个亿",
            "source": "胡润百富",
            "cmtcount": 676,
            "img": "http://ajax-api.itheima.net/images/8.webp",
            "time": "2222-10-21 06:19:37"
        }
    ]
}
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      list-style: none;
    }
    .news {
      display: flex;
      height: 120px;
      width: 600px;
      margin: 0 auto;
      padding: 20px 0;
      cursor: pointer;
    }
    .news .left {
      flex: 1;
      display: flex;
      flex-direction: column;
      justify-content: space-between;
      padding-right: 10px;
    }
    .news .left .title {
      font-size: 20px;
    }
    .news .left .info {
      color: #999999;
    }
    .news .left .info span {
      margin-right: 20px;
    }
    .news .right {
      width: 160px;
      height: 120px;
    }
    .news .right img {
      width: 100%;
      height: 100%;
      object-fit: cover;
    }
  </style>
</head>
<body>

  <div id="app">
    <ul>
      <li v-for="(item, index) in list" :key="item.id" class="news">
        <div class="left">
          <div class="title">{{item.title}}</div>
          <div class="info">
            <span>{{item.source}}</span>
            <span>{{item.time}}</span>
          </div>
        </div>
        <div class="right">
          <img :src="item.img" alt="">
        </div>
      </li>

      
    </ul>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
  <script>
    // 接口地址:http://hmajax.itheima.net/api/news
    // 请求方式:get
    const app = new Vue({
      el: '#app',
      data: {
        list:[]
      },
      async created(){
        const res = await axios.get('http://hmajax.itheima.net/api/news');
        console.log(res);
        console.log( typeof res.data.data);
        this.list = res.data.data;
      }
    })
  </script>
</body>
</html>


 


自动获取焦点

功能实现:刷新页面自动获取焦点


<!DOCTYPE html>
<html lang="zh-CN">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>示例-获取焦点</title>
  <!-- 初始化样式 -->
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reset.css@2.0.2/reset.min.css">
  <!-- 核心样式 -->
  <style>
    html,
    body {
      height: 100%;
    }
    .search-container {
      position: absolute;
      top: 30%;
      left: 50%;
      transform: translate(-50%, -50%);
      text-align: center;
    }
    .search-container .search-box {
      display: flex;
    }
    .search-container img {
      margin-bottom: 30px;
    }
    .search-container .search-box input {
      width: 512px;
      height: 16px;
      padding: 12px 16px;
      font-size: 16px;
      margin: 0;
      vertical-align: top;
      outline: 0;
      box-shadow: none;
      border-radius: 10px 0 0 10px;
      border: 2px solid #c4c7ce;
      background: #fff;
      color: #222;
      overflow: hidden;
      box-sizing: content-box;
      -webkit-tap-highlight-color: transparent;
    }
    .search-container .search-box button {
      cursor: pointer;
      width: 112px;
      height: 44px;
      line-height: 41px;
      line-height: 42px;
      background-color: #ad2a27;
      border-radius: 0 10px 10px 0;
      font-size: 17px;
      box-shadow: none;
      font-weight: 400;
      border: 0;
      outline: 0;
      letter-spacing: normal;
      color: white;
    }
    body {
      background: no-repeat center /cover;
      background-color: #edf0f5;
    }
  </style>
</head>

<body>
<div class="container" id="app">
  <div class="search-container">
    <img src="https://www.itheima.com/images/logo.png" alt="黑马倒闭了">
    <div class="search-box">
      <input type="text" v-model="words" id="inp">
      <button>搜索一下</button>
    </div>
  </div>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      words: ''
    },
    mounted(){
      // 核心思路:
      // 1. 等input框渲染出来 mounted 钩子
      // 2. 让input框获取焦点 inp.focus()
      document.querySelector('#inp').focus();
    }
  })
</script>

</body>

</html>


<!-- <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> -->


综合案例:小黑记账清单

JSON数据: https://applet-base-api-t.itheima.net/bill?creator=小黑

{
  "message": "ok",
  "data": [
    {
      "id": 88664051,
      "name": "饼干",
      "price": 666,
      "creator": "小黑"
    },
    {
      "id": 88664050,
      "name": "游戏",
      "price": 499,
      "creator": "小黑"
    },
    {
      "id": 88664049,
      "name": "丝袜",
      "price": 6000,
      "creator": "小黑"
    },
    {
      "id": 88664047,
      "name": "小喵咪",
      "price": 5000,
      "creator": "小黑"
    },
    {
      "id": 88664041,
      "name": "手机",
      "price": 1000,
      "creator": "小黑"
    },
    {
      "id": 88664040,
      "name": "电脑",
      "price": 9999,
      "creator": "小黑"
    }
  ]
}
<!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 type="text" class="form-control" v-model.trim="thingName" placeholder="消费名称" />
            <input type="text" class="form-control" v-model.number="thingPrice" placeholder="消费价格" />
            <button type="button" class="btn btn-primary" @click="addOne">添加账单</button>
          </form>

          <table class="table table-hover">
            <thead>
              <tr>
                <th>编号</th>
                <th>消费名称</th>
                <th>消费价格</th>
                <th>操作</th>
              </tr>
            </thead>
            <tbody>
              <tr v-for="(item, index) in list" :key="item.id">
                <td>{{index + 1}}</td>
                <td>{{item.name}}</td>
                <td :class="{ red: item.price >= 500}">{{item.price.toFixed(2)}}</td>
                <td><a href="javascript:;" @click="delOne(item.id)">删除</a></td>
              </tr>
              
            </tbody>
            <tfoot>
              <tr>
                <td colspan="4">消费总计: {{totalCount.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. 基本渲染
       * 2. 添加功能
       * 3. 删除功能
       * 4. 饼图渲染
       */
      const app = new Vue({
        el: '#app',
        data: {
          // list: JSON.parse(localStorage.getItem('list')) || [],
          list: [],
          thingName: '',
          thingPrice: '',
        },
        created(){
          // 重新渲染一次
          this.getList();
          // const res = await axios({
          //   url: 'https://applet-base-api-t.itheima.net/bill',
          //   method: 'GET',
          //   params:{
          //     creator: '小黑'
          //   }
          // });
          // const res = await axios.get('https://applet-base-api-t.itheima.net/bill', {
          //   params: {
          //     creator: '小黑'
          //   }
          // })
          // this.list = res.data.data;
          // console.log(res.data.data);
        },
        computed:{
          // 计算消费总价
          totalCount(){
            let sum = 0;
            this.list.forEach(function(item){
              return sum +=item.price;
            })
            return sum;
            // return this.list.reduce((sum, item) => sum + item.price, 0);
          }
        },
        mounted(){
          // 基于准备好的DOM,初始化echarts实例
          this.myChart = echarts.init(document.querySelector('#main'));
          // // 配置饼图的配置项和数据
          // let option = {
          //     // 大标题
          //   title: {
          //     text: '消费账单列表',
          //     left: 'center'
          //   },
          //   // 提示框
          //   tooltip: {
          //     trigger: 'item'
          //   },
          //   // 图例
          //   legend: {
          //     orient: 'vertical',
          //     left: 'left'
          //   },
          //   // 数据项
          //   series: [
          //     {
          //       name: '消费账单',
          //       type: 'pie',
          //       radius: '50%', // 半径
          //       data: [
          //         // { value: 1048, name: '球鞋' },
          //         // { value: 735, name: '防晒霜' }
          //       ],
          //       emphasis: {
          //         itemStyle: {
          //           shadowBlur: 10,
          //           shadowOffsetX: 0,
          //           shadowColor: 'rgba(0, 0, 0, 0.5)'
          //         }
          //       }
          //     }
          //   ]
          // };
          // // 使用刚指定的配置项和数据显示饼图
          // this.myChart.setOption(option);
          this.myChart.setOption({
            // 大标题
            title: {
              text: '消费账单列表',
              left: 'center'
            },
            // 提示框
            tooltip: {
              trigger: 'item'
            },
            // 图例
            legend: {
              orient: 'vertical',
              left: 'left'
            },
            // 数据项
            series: [
              {
                name: '消费账单',
                type: 'pie',
                radius: '50%', // 半径
                data: [
                  // { value: 1048, name: '球鞋' },
                  // { value: 735, name: '防晒霜' }
                ],
                emphasis: {
                  itemStyle: {
                    shadowBlur: 10,
                    shadowOffsetX: 0,
                    shadowColor: 'rgba(0, 0, 0, 0.5)'
                  }
                }
              }
            ]
          });
        },
        methods:{
          async getList(){
            const res = await axios({
              url: 'https://applet-base-api-t.itheima.net/bill',
              method: 'GET',
              params:{
                creator: '小黑',
              },
            });
            this.list = res.data.data;
            
            this.myChart.setOption({
              // 数据项
              series: [
                {
                  // data: [
                  //   { value: 1048, name: '球鞋' },
                  //   { value: 735, name: '防晒霜' }
                  // ]
                  data: this.list.map(item => ({ value: item.price, name: item.name })),
                }
              ]
            });
            console.log('重新绘图一次')
          },
          // 删除一条物品
          async delOne(id){
            // this.list = this.list.filter((item) => item.id !== id);
            const res = await axios({
              url: `https://applet-base-api-t.itheima.net/bill/${id}`,
              method: 'DELETE',
            });
            this.getList();
          },
          // 添加一条物品
          async addOne(){
            // 非空校验
            if(this.thingName === ''){
              alert('您未输入喔,请输入消费名称');
              return;
            }
            if(typeof this.thingPrice !== 'number'){
              alert('输入的必须是数字喔,请输入消费价格');
              return;
            }
            // 发送添加请求
            const res = await axios.post('https://applet-base-api-t.itheima.net/bill', {
              creator: '小黑',
              name: this.thingName,
              price: this.thingPrice,
            })
            // const res = await({
            //   url: 'https://applet-base-api-t.itheima.net/bill',
            //   method: 'POST',
            //     creator: '小黑',
            //     name: this.thingName,
            //     price: this.thingPrice,
            // });
            // console.log('发送一次添加请求');
            // this.list.unshift({
            //   id: this.list[0].id + 1,
            //   name: this.thingName,
            //   price: this.thingPrice,
            // });
            // 重新渲染一次
            this.getList();
            // console.log('添加成功后,重新渲染一次');
            this.thingName = '';
            this.thingPrice = '';
            // console.log('执行一次添加功能');
          }
        },
        // watch:{
        //   fruitList:{
        //     handler(newValue){
        //       localStorage.setItem('list', JSON.stringify(newValue));
        //     }
        //   }
        // }
      })
    </script>
  </body>
</html>



<!-- <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> -->



工程化开发入门

Vue CLI v5.0.8

1.工程化开发和脚手架

安装教程亲测可用:Vue脚手架的安装(保姆级教程)_vscode安装vue脚手架-CSDN博客

脚手架Vue CLI 是Vue官方提供的一个全局命令工具,基于Node.js

搭建教程


脚手架目录


注意点
更改镜像源
npm config set registry https://registry.npmmirror.com
 node权限

一般node安装在非C盘,就会权限不足,需要手动调整一下权限

修改服务器端口号


2.项目运行流程

使用脚手架的项目,不在容器里直接编写模板语法,而是在App.vue中提供结构渲染

main.js入口文件的作用
// 此入口文件的核心作用:导入App.vue,基于App.vue创建结构渲染index.html

// 1.导入 Vue 核心包
import Vue from 'vue'

// 2.导入 App.vue 根组件
import App from './App.vue'

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

// 3. Vue实例化,提供render方法 -> 基于App.vue创建结构渲染index.html
new Vue({
  // el: '#app' 的作用:和$mount('#app')作用一致,用于指定Vue所管理容器
  render: h => h(App),
  // render: (h) => { return h(App)} 基于App创建元素结构
}).$mount('#app')

3.组件化开发&根组件


App.vue的样式支持less
  • 安装依赖
npm i less less-loader -D
  • style标签添加lang

4.普通组件注册

上面了解了根组件,下面了解普通组件

组件注册的两种方式


局部注册
  • 生成骨架


 案例演示

  • 创建组件

  • 根组件配置

App.vue

<template>
  <div class="App">
    <!-- 头部组件 -->
    <HmHeader></HmHeader>
    <!-- 主体组件 -->
    <HmMain></HmMain>
    <!-- 底部组件 -->
    <HmFooter></HmFooter>
  </div>
</template>

<script>
import HmHeader from "./components/HmHeader.vue"
import HmMain from "./components/HmMain.vue"
import HmFooter from "./components/HmFooter.vue"

export default {
  components: {
    // '组件名': 组件对象 组件名建议驼峰命名
    HmHeader: HmHeader,
    HmMain: HmMain,
    HmFooter: HmFooter,
  }
}
</script>

<style>
    .App {
        width: 500px;
        height: 800px;
        background-color: rgb(129, 201, 239);
        margin: 0 auto;
        padding: 20px;
    }
</style>
  • 分别编写每个组件

HmHeader.vue

<template>
    <div class="hm-header">
      我是hm-header
    </div>
  </template>
  
  <script>
  export default {
  
  }
  </script>
  
  <style>
      .hm-header{
          height: 100px;
          line-height: 100px;
          text-align: center;
          background-color: #826AA4;
          color: white;
      }
  </style>

HmMain.vue

<template>
  <div class="hm-main">
    我是hm-main
  </div>
</template>

<script>
export default {

}
</script>

<style>
    .hm-main{
        height: 520px;
        line-height: 520px;
        text-align: center;
        background-color: #FF9A41;
        color: white;
        margin-bottom: 20px;
        margin-top: 20px;
    }
</style>

HmFooter.vue

<template>
  <div class="hm-footer">
    我是hm-footer
  </div>
</template>

<script>
export default {

}
</script>

<style>
  .hm-footer{
    height: 100px;
    line-height: 100px;
    text-align: center;
    background-color: #6A93C4;
    color: white;
  }
</style>


全局注册

案例演示

通用的组件不适合局部注册

  • 创建组件

<template>
  <button class="hm-button">通用按钮</button>
</template>

<script>
export default {

}
</script>

<style>
    .hm-button{
        height: 50px;
        line-height: 50px;
        text-align: center;
        background-color: #349D55;
        border-radius: 5px;
    }
</style>
  • 在main.js导入与注册组件

// 导入需要全局注册的组件
import HmButton from './components/HmButton.vue'

// 调用Vue.component 进行全局注册
// Vue.component('组件名', 组件对象)
Vue.component('HmButton', HmButton)
  • 使用组件


综合案例:小兔鲜首页

老师:先都做成局部的,然后对通用的修改成全局的

项目准备

字体样式和图片都不展示了,下面是两个css文件

  • base.css
/* 去除常见标签默认的 margin 和 padding */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

/* 设置网页统一的字体大小、行高、字体系列相关属性 */
body {
  font: 16px/1.5  "Microsoft Yahei",
    "Hiragino Sans GB", "Heiti SC", "WenQuanYi Micro Hei", sans-serif;
  color: #333;
}

/* 去除列表默认样式 */
ul,
ol {
  list-style: none;
}

/* 去除默认的倾斜效果 */
em,
i {
  font-style: normal;
}

/* 去除a标签默认下划线,并设置默认文字颜色 */
a {
  text-decoration: none;
  color: #333;
}

/* 设置img的垂直对齐方式为居中对齐,去除img默认下间隙 */
img {
  width: 100%;
  height: 100%;
  vertical-align: middle;
}

/* 去除input默认样式 */
input {
  border: none;
  outline: none;
  color: #333;
}

h1,
h2,
h3,
h4,
h5,
h6 {
  font-weight: 400;
}


/* 双伪元素清除法 */
.clearfix::before,
.clearfix::after {
  content: "";
  display: table;
}
.clearfix::after {
  clear: both;
}
  • common.css
/* 公共的全局样式 */
.wrapper {
  margin: 0 auto;
  width: 1240px;
}

.title {
  display: flex;
  justify-content: space-between;
  margin-top: 40px;
  margin-bottom: 30px;
  height: 42px;
}
.title .left {
  display: flex;
  align-items: flex-end;
}
.title .left h3 {
  margin-right: 35px;
  font-size: 30px;
}
.title .left p {
  padding-bottom: 5px;
  color: #A1A1A1;
}
.title .right {
  line-height: 42px;
}
.title .right .more {
  color: #A1A1A1;
}
.title .right .iconfont {
  margin-left: 10px;
}

组件注册

使用局部注册

  • 新建组件

  • 引入与注册

  • App.vue 
<template>
  <div class="App">
    <!-- 快捷链接 -->
    <XtxShortCut></XtxShortCut>
    <!-- 顶部导航 -->
    <XtxHeaderNav></XtxHeaderNav>
    <!-- 轮播区域 -->
    <XtxBanner></XtxBanner>
    <!-- 新鲜好物 -->
    <XtxNewGoods></XtxNewGoods>
    <!-- 热门品牌 -->
    <XtxHotBrand></XtxHotBrand>
    <!-- 最新专题 -->
    <XtxTopic></XtxTopic>
    <!-- 版权底部 -->
    <XtxFooter></XtxFooter>
  </div>
</template>

<script>
import XtxShortCut from './components/XtxShortCut.vue'
import XtxHeaderNav from './components/XtxHeaderNav.vue'
import XtxBanner from './components/XtxBanner.vue'
import XtxNewGoods from './components/XtxNewGoods.vue'
import XtxHotBrand from './components/XtxHotBrand.vue'
import XtxTopic from './components/XtxTopic.vue'
import XtxFooter from './components/XtxFooter.vue'
export default {
    components: { 
      XtxShortCut,
      XtxHeaderNav, 
      XtxBanner, 
      XtxNewGoods, 
      XtxHotBrand, 
      XtxTopic, 
      XtxFooter },

}
</script>

<style>

</style>

工程化进阶

组件的三大组成部分

组件的样式冲突scoped

每个组件应该有着自己独立的样式,推荐加上scoped

scoped原理


data必须是一个函数

data从变量改成函数有什么好处?

出了格式有些不一样,其他用法与之前是一样的

data (){
    return {
    
    }
}
  • 之前的用法

  • 现在的用法


组件通信

什么是组件通信

通信流程图

不同 的组件关系,通信方案不一样的


组件通信解决方案

父传子(props)

props详解:【Vue2.x】props技术详解-CSDN博客

props可以理解为标签的自定义属性


子传父($emit)


非父子(event bus事件总线)

  • 工具类utils / EventBus.js
import Vue from 'vue'

// 创建一个空vue实例
const Bus  =  new Vue()
// 导出vue实例
export default Bus
  • BaseA.vue(接收方)

什么时候开始监听呢?因该是在dom操作之前,数据准备好之后,最好是在首次渲染之前,也就是created

  • BaseB.vue(发布方)


非父子(provide inject)

实现跨层级共享数据,不需要爷爷 -> 父亲 -> 孙子,而是直接 爷爷 -> 孙子

注意点

这种写法,简单类型数据是非响应式的,也就是数据不会随时变化


进阶语法

v-model原理

模版中获取形参

在行内拿形参不能写e,要写$event


v-model处理表单类组件封装

简化代码

图片里文字进行更改:方法名只能取input,prop属性名只能取value,不能随便取,否则不能简化


sync修饰符

上面使用v-model简写,虽然能实现父子组件双向绑定,但是简写方法的props属性名必须是value,明显不合适,无语义

与v-model效果相同,语法稍有不同

达到了v-model的效果,而且能自定义props属性名


ref 和 $refs获取组件dom

  • 比原生JS的querySelector好在哪?

querySelector是从整个页面查找,$refs是从当前组件查找

  • 要在模板渲染成功后再使用

  • 使用方法


Vue异步更新、$nextTick

Vue要先渲染结构,操作dom需要异步更新dom(提升性能),渲染完成才能操作

<template>
  <div class="app">
    <div v-if="isShowEdit">
      <input type="text" v-model="editValue" ref="inp" />
      <button>确认</button>
    </div>
    <div v-else>
      <span>{{ title }}</span>
      <button @click="editFn">编辑</button>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      title: '大标题',
      isShowEdit: false,
      editValue: '',
    }
  },
  methods: {
    editFn() {
      // 1.显示文本框
      this.isShowEdit = true
      // 2.让文本框聚焦 (会等dom更新完之后 立马执行nextTick中的回调函数)
      // this.$nextTick(() => {
      //   console.log(this.$refs.inp)
      //   this.$refs.inp.focus()
      // })

      setTimeout(() => {
        this.$refs.inp.focus()
      }, 0)
    },
  },
}
</script>

<style>
</style>

下一篇:Vue笔记(三)-CSDN博客

相关推荐

  1. Vue笔记)基本语法

    2024-06-14 14:26:03       49 阅读
  2. <span style='color:red;'>VUE</span><span style='color:red;'>笔记</span>

    VUE笔记

    2024-06-14 14:26:03      46 阅读
  3. Vue笔记

    2024-06-14 14:26:03       35 阅读
  4. 前端开发学习笔记() : Vue3 常用指令和功能

    2024-06-14 14:26:03       40 阅读
  5. Vue前端规范【

    2024-06-14 14:26:03       39 阅读
  6. Vue 面试题(

    2024-06-14 14:26:03       47 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-06-14 14:26:03       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-06-14 14:26:03       106 阅读
  3. 在Django里面运行非项目文件

    2024-06-14 14:26:03       87 阅读
  4. Python语言-面向对象

    2024-06-14 14:26:03       96 阅读

热门阅读

  1. LeetCode题练习与总结:单词接龙Ⅱ--126

    2024-06-14 14:26:03       27 阅读
  2. c++相关的数据结构

    2024-06-14 14:26:03       24 阅读
  3. TF-IDF算法:揭秘文本数据的权重密码

    2024-06-14 14:26:03       25 阅读
  4. 银行外汇存款业务功能测试全面指南

    2024-06-14 14:26:03       23 阅读
  5. 爬虫学习————request模块

    2024-06-14 14:26:03       32 阅读
  6. react-router 的路由匹配逻辑

    2024-06-14 14:26:03       32 阅读
  7. Python 学习 第二册 对第一册的一些补充

    2024-06-14 14:26:03       30 阅读