码农之家

Vue小练习合集

🍔练习源码来源👇:

Web前端/5-框架/V2.x/练习 · lidongxu/B站_配套代码 – 码云 – 开源中国 (gitee.com)

做任何需求的套路:

  1. 先铺设标签和样式
  2. 铺设数据(固定数据/js动态创建数据/后台返回数据)
  3. 写交互,多思考用户在页面上的动作,实现对应功能的代码
  4. 如果需要把用户输入的值/动作的结果,返回给后台,则调用后台接口即可

🍔点击说笑话

知识点

  1. 掌握创建vue/cli脚手架项目
  2. 掌握插值表达式的使用
  3. 掌握事件绑定
  4. 掌握随机数公式
  5. this.访问变量

过程

  1. 准备标签和数据
  2. 按钮绑定点击事件 btnFn 和方法methods里
  3. 在方法中产生随机数 getRanFn
  4. 去数组里换出对应的笑话,赋予给页面的变量
    • 设立变量——下标index,随机字符串str
    • 最后把str赋值给word,在页面用插值渲染
  5. 变量值改变,视图自动更新
<template>
  <div>
    <p>{{ word }}</p>
    <button @click="btnFn">点击说笑话</button>
  </div>
</template>

<script>
export default {
  data(){
    return {
      word: '这里是一条笑话',
      jockArr: ['我去相亲网站去了, 那你找到对象了吗? 不! 我找到了他们网站的一个Bug', '讲述一下车怎么翻沟里了。我坐在副驾说沟!沟!沟! 朋友说:欧嘞,欧嘞。欧嘞 结果车就翻里了。', '几位大哥开车去钓鱼,车陷草里了,然后几个大神说把草烧了就出来了,然后就放火烧了,结果就烧没了。']
    }
  },
  methods:{
    // 产生随机数的方法(公式)
    getRandFn(min,max){
      return Math.floor(Math.random()* (max-min+1)+min)
    },
    // 点击方法
    btnFn(){
      // 调用上面方法,产生一个随机的下标
      let index=this.getRandFn(0,this.jockArr.length-1)
      // 换出一个随机的字符串
      let str=this.jockArr[index]
      //设置给word变量,来页面展示
      this.word=str

      //this.word=this.jockArr[this.getRandFn(0,this.jockArr.length-1)]
    }
  }
}
</script>

🍔点击翻转世界

知识点

  1. 掌握插值表达式的使用
  2. 掌握字符串和数组互相转换的方法
  3. 掌握数组翻转所有元素方法
  4. this访问变量

过程

  1. 准备标签和变量
  2. 绑定点击事件和方法
  3. 把变量里字符串转成数组
  4. 调用数组翻转方法
  5. 再把数组里的元素拼接回字符串
<template>
  <div>
    {{ message }}
    <button  @click="btnRn">翻转世界</button>
  </div>
</template>

<script>
export default {
  data(){
    return {
      message:'Hello,World'
    }
  },
    methods:{
      btnRn(){
        let arr = this.message.split("")
        arr.reverse()
        this.message=arr.join("")
        //this.message=((this.message.split("")).reverse()).join("")
      }
    }
}
</script>
  1. split() 方法返回的是一个新的数组。这个数组包含了原始字符串根据指定的分隔符被分割后得到的所有子字符串。原始字符串本身并不会被修改
  2. reverse() 方法会原地(in-place)反转数组元素的顺序,也就是说它会直接修改原数组
  3. join() 方法并不返回一个新数组,而是返回一个由数组中所有元素连接而成的字符串

🍔折叠面板

知识点

  1. 掌握事件绑定和方法定义
  2. 掌握控制标签显示/隐藏的指令
  3. 掌握对运行过程中变量值的理解
  4. 掌握取反的思想

过程

  1. 准备标签和样式
  2. 下载less
  3. 绑定点击事件和方法
  4. 定义变量,使用v-show指令控制目标显示/隐藏
  5. 在事件方法中,控制变量的值,来实现显示/隐藏的效果

安装less

yarn add less@3.0.4 less-loader@5.0.0 -D
<template>
  <div id="app">
    <h3>案例:折叠面板</h3>
    <div>
      <div class="title">
        <h4>芙蓉楼送辛渐</h4>
        <span class="btn" @click="btnChange">
          收起
        </span>
      </div>
      <div class="container" v-show="isShow">
        <p>寒雨连江夜入吴,</p>
        <p>平明送客楚山孤。</p>
        <p>洛阳亲友如相问,</p>
        <p>一片冰心在玉壶。</p>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      isShow:true
    }
  },
  methods:{
    btnChange(){
      // 也可以写成if判断的形式,但我觉得写取反这个简单
      this.isShow = !this.isShow
    }
  }
}
</script>

<style lang="less">
body {
  background-color: #ccc;
  #app {
    width: 400px;
    margin: 20px auto;
    background-color: #fff;
    border: 4px solid blueviolet;
    border-radius: 1em;
    box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.5);
    padding: 1em 2em 2em;
    h3 {
      text-align: center;
    }
    .title {
      display: flex;
      justify-content: space-between;
      align-items: center;
      border: 1px solid #ccc;
      padding: 0 1em;
    }
    .title h4 {
      line-height: 2;
      margin: 0;
    }
    .container {
      border: 1px solid #ccc;
      padding: 0 1em;
    }
    .btn {
      /* 鼠标改成手的形状 */
      cursor: pointer;
    }
  }
}
</style>

🍔逛水果店

知识点

  1. 创建vue/cli脚手架项目
  2. 插值表达式使用
  3. v-model的使用
  4. 事件绑定和传值
  5. data和methods的使用
  6. this访问变量

过程

  1. 准备标签和样式
  2. 准备变量,铺设页面(准备的时候多思考都哪里需要变量,然后可以先都写上)
  3. 绑定输入框值
  4. 点击计算,给各个属性赋值
<template>
  <div>
    <table width=800 style="text-align: center; margin: 0 auto;" border=1>
            <caption>欢迎光临_vue开发的收银系统_水果店</caption>
            <tr>
                <th>苹果{{ price }}  ¥ / 斤, 折扣 &lt;{{ cut*10 }}  折 &gt;</th>
            </tr>
            <tr>
                <td>
                    请输入你要购买的斤数 <input v-model="kg" type="number"   placeholder="斤数">
                </td>
            </tr>
            <tr>
                <td>
                    <button @click="buyOver">结账买单~</button>
                </td>
            </tr>
            <tr>
                <td>
                    结账单:
                    <span>总价:{{ allPrice }}  ¥元</span>
                    <span>折后价:{{ cutPrice }}  ¥元</span>
                    <span>省了: {{ savePrice }} ¥元</span>
                </td>
            </tr>
        </table>
  </div>
</template>

<script>
export default {
  data(){
    return{
      price:'10',
      cut:'0.8',
      kg:'0',
      allPrice:'0',
      cutPrice:'0',
      savePrice:'0'
    }
  },
  methods:{
    buyOver(){
      this.allPrice=this.kg*this.price,
      this.cutPrice=(this.kg*this.price)*this.cut,
      this.savePrice=this.allPrice-this.cutPrice
    }
  }
}
</script>

<style>

</style>

🍔选择喜欢

知识点

  1. v-model的使用
  2. v-for的使用

过程

  1. 准备标签
  2. 准备数组,循环标签和数组
  3. 准备v-model和value,收集用户选中的频道
  4. 把用户选中数据,循环渲染出来
<template>
<div>
  <h2>请选择你喜欢的专栏</h2>
  <!-- !!! v-model="selArr" :value="item" -->
  <div style="display: inline-block;" v-for="(item,index) in arr" :key="index">
    <input v-model="selArr" :value="item" type="checkbox">{{ item }}
  </div>
  <br>
  <h2>你选中了:</h2>
  <div v-for="(item) in selArr" :key="item.id">
    {{ item }}
  </div>
</div>
</template>

<script>
export default {
  data(){
    return{
      //v-model="isCheck" 错
      //isCheck:false,
      arr:["科幻", "喜剧", "动漫", "冒险", "科技", "军事", "娱乐", "奇闻"],
      selArr:[]
    }
  }
}
</script>

<style>

</style>

疑惑点👇:

🍔购物车

知识点

  1. 掌握v-for的使用
  2. 掌握对象取值的使用tr
  3. 掌握数组删除元素的方法
  4. 掌握显示/隐藏标签的指令
  5. 掌握数据驱动页面的思想

需求

  1. 根据初始数据,铺设表格页面
  2. 点击删除按钮,删除对应的数据
  3. 当没有数据时,显示一条提示消息(剩余数组的长度为0)
<template>
  <div id="app">
    <table class="tb">
      <tr>
        <th>编号</th>
        <th>品牌名称</th>
        <th>创立时间</th>
        <th>操作</th>
      </tr>
      <!-- 循环渲染的元素tr -->
      <tr  v-for="item in list" :key="item.id">
        <td>{{ item.id }}</td>
        <td>{{ item.name }}</td>
        <td>{{ item.time }}</td>
        <td>
          <!-- 这里需要传的是item.id,而不只是id -->
          <button @click="delFn(item.id)">删除</button>
        </td>
      </tr>
      <tr v-if="list.length===0">
        <td colspan="4">没有数据咯~</td>
      </tr>
    </table>
  </div>
</template>

<script>
export default {
  data() {
    return {
      list: [
        { id: 101, name: "奔驰", time: "2020-08-01" },
        { id: 102, name: "宝马", time: "2020-08-02" },
        { id: 103, name: "奥迪", time: "2020-08-03" },
      ],
    };
  },
  methods:{
    delFn(id){
      // 这是根据id删(推荐)
      this.list=this.list.filter(item=>item.id!== id)
      // 也可以根据下标删
      //let index=this.list.findIndex(item=> item.id === id)
      //this.list.splice(index,1)
    }
  }
};
</script>

<style>
#app {
  width: 600px;
  margin: 10px auto;
}

.tb {
  border-collapse: collapse;
  width: 100%;
}

.tb th {
  background-color: #0094ff;
  color: white;
}

.tb td,
.tb th {
  padding: 5px;
  border: 1px solid black;
  text-align: center;
}

.add {
  padding: 5px;
  border: 1px solid black;
  margin-bottom: 10px;
}
</style>

🍔点名系统

知识点

  1. 掌握事件绑定和方法定义
  2. 掌握变量的定义和插值表达式的使用
  3. 掌握计时器创建和销毁
  4. 掌握随机数公式

过程

  1. 准备标签
  2. 准备变量,替换到名字处
  3. 准备名字数组
  4. 开始——按钮点击事件和方法,并创建随机数函数
  5. 在点击事件中,调用随机函数,传入范围,然后得到随机数换出名字
  6. 创建计时器,每间隔一段时间,让上面代码再执行一次看效果
  7. 点击停止,销毁计时器,把计时器保存到data里的变量中使用

问题: 频繁点击导致计时器多次创建

  • 频繁点击会累加计时器数量,效果就是越来越快
  1. 解决方案一: 点击按钮后,先做一个清除计时器的操作,然后再创建
  2. 带来了新的问题: 一直点击,一直销毁,计时器没法执行,名字就卡住不动了
  3. 解决方案二: 变量来做开关
<template>
  <div>
    <h2>随机点名</h2>
    <div class="box">
      <span>名字是:</span>
      <div class="qs">{{ userName }}</div>
    </div>
    <div class="btns">
      <button @click="btnStart" class="start">开始</button>
      <button @click="btnEnd"  class="end">结束</button>
    </div>
  </div>
</template>

<script>
export default {
  data(){
    return{
      userName:'这里显示名字',
      nameArr:['唐周','应渊','李莲花','李相夷','禹司凤'],
      isHave:false, // 保存计时器状态
      timer:null //保存计时器本身(一会用于销毁)
    }
  },
  methods:{
    getRand(min,max){
      return Math.floor(Math.random()*(max-min+1)+min)
    },
    btnStart(){
      if(this.isHave===true) return

      this.isHave=true
      this.timer=setInterval(()=>{
        this.userName=this.nameArr[this.getRand(0,this.nameArr.length-1)]
      },300)
    },
    btnEnd(){
      clearInterval(this.timer)
      this.isHave=false
    }
  }
  
};
</script>

<style>
* {
  margin: 0;
  padding: 0;
}

h2 {
  text-align: center;
}

.box {
  width: 600px;
  margin: 50px auto;
  display: flex;
  font-size: 25px;
  line-height: 40px;
}

.qs {
  width: 450px;
  height: 40px;
  color: red;
}

.btns {
  text-align: center;
}

.btns button {
  width: 120px;
  height: 35px;
  margin: 0 50px;
}
</style>

🍔协议倒计时

知识点

  1. 掌握计时器的使用
  2. 掌握插值表达式使用
  3. 掌握动态属性控制禁用状态
  4. 掌握倒计时的做法

需求

  1. 按钮禁用
  2. 倒计时提示

过程

  1. 准备标签和样式
  2. 定义变量,代表显示的秒数,定义变量代表显示文字
  3. 在mounted函数中(页面创建完会自动触发),定义计时器,每秒执行
  4. 定义局部变量代表剩余秒数,做倒计时效果
  5. 把剩余秒数赋予给页面显示的变量
  6. 判断当倒计时结果为0时,停止计时器
  7. 放开按钮的禁用效果,修改按钮上显示的文字
<template>
  <div>
    <textarea name="" id="" cols="30" rows="10">
        用户注册协议
        欢迎注册成为京东用户!在您注册过程中,您需要完成我们的注册流程并通过点击同意的形式在线签署以下协议,请您务必仔细阅读、充分理解协议中的条款内容后再点击同意(尤其是以粗体或下划线标识的条款,因为这些条款可能会明确您应履行的义务或对您的权利有所限制)。
        【请您注意】如果您不同意以下协议全部或任何条款约定,请您停止注册。您停止注册后将仅可以浏览我们的商品信息但无法享受我们的产品或服务。如您按照注册流程提示填写信息,阅读并点击同意上述协议且完成全部注册流程后,即表示您已充分阅读、理解并接受协议的全部内容,并表明您同意我们可以依据协议内容来处理您的个人信息,并同意我们将您的订单信息共享给为完成此订单所必须的第三方合作方(详情查看
    </textarea>
    <br>
    <!-- 属性不给值, 默认就是true值 -->
    <button class="btn" :disabled="isDis">我已经阅读用户协议({{ sTime }})</button>
  </div>
</template>

<script>
export default {
  data(){
    return{
      sTime:60,
      isDis:true// 动态属性
    }
  },
  mounted(){
    let timer=setInterval(()=>{
      this.sTime--
      
      if(this.sTime==0){
        clearInterval(timer)
        this.isDis=false
      }
    },1000)
  }
  
};
</script>

🍔加加减减

知识点

  1. 掌握事件绑定和方法定义
  2. 掌握数组的添加和删除元素的方法
  3. 掌握事件传参的使用
  4. 掌握 数据驱动视图改变 的思想

过程

  1. 准备标签,样式和固定数组
  2. 先用v-for循环铺设li和内容和按钮
  3. 实现生成按钮点击事件和方法
  4. 每次点击产生随机数,放入数组里,驱动视图li更新
  5. 实现删除按钮点击事件和方法,传递下标索引
  6. 在事件方法中,删除数据里相应元素,驱动视图li更新
<template>
  <div id="app">
    <ul>
      <li v-for="(item,index) in arr" :key="index">
        <span>{{ item }}</span>
        <button @click="btnDel(index)">删除</button>
      </li>
    </ul>
    <button @click="btnAdd">生成</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      arr: [1, 5, 3]
    }
  },
  methods:{
    btnAdd(){
      // 生成0~99的随机数
      this.arr.push(Math.floor(Math.random()*100))
    },
    btnDel(index){
      this.arr.splice(index,1)
    }
  }
}
</script>

🍔帅哥美女走一走

知识点

  1. 掌握事件绑定和方法定义
  2. 掌握数组的头部删除和尾部新增的方法
  3. 掌握v-for

过程

  • 重点是事件方法里的js逻辑:
    • 在事件方法里,把头部数据删除并返回这个值,添加到数组的末尾
<template>
  <div id="app">
    <ul>
      <li v-for="(item,index) in myArr" :key="index">
        {{ item }}
      </li>
    </ul>
    <button @click="btnGo">走一走</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      myArr: ["帅哥", "美女", "程序猿"],
    }
  },
  methods:{
    btnGo(){
      this.myArr.push(this.myArr[0])
      this.myArr.shift()
      
      // 可以合并起来写
      //this.myArr.push(this.myArr.shift())
    }
  }
}
</script>

🍔买点书练习

知识点

  1. v-for
  2. 掌握事件绑定和传参
  3. 掌握计算属性的使用

过程

  1. 准备标签和固定数据
  2. v-for
  3. 买书按钮绑定点击事件,在事件中,给数据对象增加数量
  4. 总价的计算属性里,统计数组里对象的数量和单价的累积和 reduce
<template>
  <div>
    <p>请选择你要购买的书籍</p>
    <ul>
      <li v-for="(item,index) in arr" :key="index">
        <span>{{ item.name }}</span>
        <button @click="buyFn(index)">买它</button>
      </li>
    </ul>
    <ul>
    </ul>
    <table border="1" width="500" cellspacing="0">
      <tr>
        <th>序号</th>
        <th>书名</th>
        <th>单价</th>
        <th>数量</th>
        <th>合计</th>
      </tr>
      <tr v-for="(item,index) in arr" :key="index">
        <th>{{ index+1 }}</th>
        <th>{{ item.name }}</th>
        <th>{{ item.price }}</th>
        <th>{{ item.count }}</th>
        <th>{{ item.price*item.count }}</th>
      </tr>
    </table>
    <p>总价格为: {{ totalPrice }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      arr: [
        {
          name: "水浒传",
          price: 107,
          count: 0,
        },
        {
          name: "西游记",
          price: 192,
          count: 0,
        },
        {
          name: "三国演义",
          price: 219,
          count: 0,
        },
        {
          name: "红楼梦",
          price: 178,
          count: 0,
        },
      ],
    };
  },
  methods:{
    buyFn(index){
      this.arr[index].count++
    }
  },
  computed:{
    totalPrice(){
      return this.arr.reduce((sum,item)=>{
        sum+=item.price*item.count
        return sum
      },0)
  }
}

}
</script>

🍔选你爱 我求和

知识点

  1. 掌握v-model的使用
  2. 掌握v-model收集复选框value值的技巧
  3. 掌握v-for
  4. 掌握计算属性的使用
<template>
  <div>
    <!-- 无id时, 可以使用index(反正也是就地更新) -->
    <!-- :value="item"收集复选框value的值 -->
    <div
      style="display: inline-block"
      v-for="(item,index) in arr" 
      :key="index"
    >
      <input  
      type="checkbox"
      v-model="selArr"
      :value="item"
       />
      <span>{{ item }}</span>
    </div>
    <p>你选中的元素, 累加的值和为:{{ totalAdd }} </p>
  </div>
</template>

<script>
export default {
    data(){
        return {
            arr: [9, 15, 19, 25, 29, 31, 48, 57, 62, 79, 87],
            selArr:[]
        }
    },
    computed:{
      totalAdd(){
        return this.selArr.reduce((sum,item)=>{
          sum+=item
          return sum
        },0)
      }
    }
};
</script>

🍔导航切换高亮

知识点

  1. 动态class
  2. v-for运用
  3. 事件绑定和传参

过程

  1. 准备标签和数据
  2. 绑定动态class
  3. 绑定点击事件+传递索引
  4. 在动态class判断 循环的索引点击保存的索引 是否相等
<template>
<div class="wrap">
<div class="nav_left" id="navLeft">
<div class="nav_content">
<span 
v-for="(item,index) in arr" 
:key="index" 
:class="{active:index=== selIndex}"
@click="btnActive(index)"
>
{{ item.first_name }}
</span>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
selIndex:0,
arr: [
{
first_id: "0",
first_name: "热门"
},
{
first_id: "621",
first_name: "\u5496\u5561",
},
{
first_id: "627",
first_name: "\u996e\u98df",
},
{
first_id: "279",
first_name: "\u7537\u88c5",
},
{
first_id: "294",
first_name: "\u5973\u88c5",
},
{
first_id: "122",
first_name: "\u773c\u955c",
},
{
first_id: "339",
first_name: "\u5185\u8863\u914d\u9970",
},
{
first_id: "391",
first_name: "\u6bcd\u5a74",
},
{
first_id: "35",
first_name: "\u978b\u9774",
},
{
first_id: "39",
first_name: "\u8fd0\u52a8",
},
{
first_id: "153",
first_name: "\u7bb1\u5305",
},
{
first_id: "119",
first_name: "\u7f8e\u5986\u4e2a\u62a4",
},
{
first_id: "355",
first_name: "\u5bb6\u7eba",
},
{
first_id: "51",
first_name: "\u9910\u53a8",
},
{
first_id: "334",
first_name: "\u7535\u5668",
},
{
first_id: "369",
first_name: "\u5bb6\u88c5",
},
{
first_id: "10",
first_name: "\u5bb6\u5177",
},
{
first_id: "223",
first_name: "\u6570\u7801",
},
{
first_id: "429",
first_name: "\u6c7d\u914d",
},
{
first_id: "546",
first_name: "\u5065\u5eb7\u4fdd\u5065",
},
{
first_id: "433",
first_name: "\u5b9a\u5236",
},
],
};
},
methods:{
btnActive(index){
this.selIndex=index
}
}
};
</script>
<style>
.wrap {
width: 100%;
display: flex;
margin: 0.2rem 0 0 0;
position: relative;
}
/*左侧的导航样式*/
.nav_left {
width: 21.1875rem;
overflow: scroll;
}
.nav_left::-webkit-scrollbar {
display: none;
}
.nav_content {
white-space: nowrap;
padding: 0 0.7rem;
}
.nav_content span {
display: inline-block;
padding: 0.4rem 0.6rem;
font-size: 0.875rem;
}
.nav_content .active {
border-bottom: 2px solid #7f4395;
color: #7f4395;
}
.nav_left,
.down {
float: left;
}
/*右侧导航部分*/
.down {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
}
.gray {
color: gray;
display: inline-block;
vertical-align: middle;
}
</style>

🍔学生管理系统

知识点

  1. 对象引用关系和拷贝
  2. 事件绑定传参
  3. 数组的各种方法的运用

需求

  1. 铺设列表
  2. 新增学生信息
    • v-model绑定表单里的值
    • 按钮绑定事件,点击后插入数组新对象
    • (坑:对象插入到数组里还会相互引用,用$set解决)
  3. 编辑+修改
    • 编辑事件传入要编辑的数据对象
    • 拷贝数组里的对象回显给newUser
    • 添加/修改按钮判断——一个方法里,但要区分出来要实现的功能是添加还是编辑(重点,难)
  4. 编辑回显的方法中
    • (既需要传item,也需要传index)
  5. 删除
    • 绑定点击事件,传入对应索引
    • 删除数组里的数据
  6. 其他的完善功能
    • 无论添加/修改,按了以后清空输入框
    • 判断下如果是空的要提示用户
    • 表格没有数据时,表格隐藏
<template>
<div id="app">
<div>
<span>姓名:</span>
<input v-model="newUser.name" type="text" />
</div>
<div>
<span>年龄:</span>
<input v-model="newUser.age" type="number" />
</div>
<div>
<span>性别:</span>
<select v-model="newUser.sex">
<option value="男"></option>
<option value="女"></option>
</select>
</div>
<div>
<button @click="addOrEditBtn">添加/修改</button>
</div>
<div>
<table
border="1"
cellpadding="10"
cellspacing="0"
v-show="this.arr.length>0"
>
<tr>
<th>序号</th>
<th>姓名</th>
<th>年龄</th>
<th>性别</th>
<th>操作</th>
</tr>
<tr v-for="(item,index) in arr" :key="index" >
<td>{{ index+1 }}</td>
<td>{{ item.name }}</td>
<td>{{ item.age }}</td>
<td>{{ item.sex }}</td>
<td>
<button @click="delBtn(index)">删除</button>
<button @click="editBtn(item,index)">编辑</button>
</td>
</tr>
</table>
</div>
</div>
</template>
<script>
export default {
data(){
return{
editIndex:-1,//正在编辑的索引,默认打开网页是没有的
newUser:[// 输入框表单绑定的对象
{
name:'',
age:0,
sex:' '
}
],
arr:[// 数据源
{
name:'成毅',
age:19,
sex:'男'
},
{
name:'谢景行',
age:23,
sex:'男'
},
{
name:'沈妙',
age:16,
sex:'女'
}
]
}
},
methods:{
addOrEditBtn(){
if(this.newUser.name ===''||
this.newUser.age === 0||
this.newUser.sex === '')
{
alert('请输入完整信息')
return
}
if(this.editIndex===-1){
//添加
//拷贝出来然后生成新的全新的对象了(需要新的,不然表格里数据会随着输入框同步修改)
this.arr.push({...this.newUser})
}else{
//编辑
// 为什么用set——因为我想修改数组里的某个值导致v-for的更新
this.$set(this.arr,this.editIndex,{...this.newUser})
}
this.newUser={
name:'',
age:0,
sex:''
}
},
editBtn(item,index){ //点编辑会回显
this.newUser={...item}// 回显
this.editIndex=index // 正在编辑的索引
},
delBtn(index){
this.arr.splice(index,1)
}
}
}
</script>

$set方法👇

🍔组件—喜欢小狗狗吗(页面组件复用)

知识点

  1. 掌握脚手架工程的创建
  2. 掌握Vue单文件的开发方式
  3. 掌握Vue中组件的创建和使用

使用组件无非三步:

  1. 导入
  2. 注册
  3. 使用
<template>
<div>
<likeDog></likeDog>
<likeDog></likeDog>
<likeDog></likeDog>
</div>
</template>
<script>
import likeDog from './components/likeDog.vue'
export default {
components:{
likeDog
}
};
</script>
<style>
</style>

🍔组件—点击文字变色

1. 直接在子组件中实现此功能

知识点

  1. 掌握组件的基本使用
  2. 掌握事件绑定和方法定义
  3. 掌握组件的特点
  4. 掌握随机颜色字符串的定义

过程

  • 目标:点击名字——变随机背景颜色
  1. p标签绑定点击事件
  2. 定义产生随机颜色字符串的方法
  3. 点击触发,产生随机颜色
  4. 定义data里变量,去接收随机颜色字符串,使用到模板标签动态style属性中
  • (模板里里不能使用局部变量)
    反引号里允许使用${}

在Vue中,模板中不能使用colorStr,它是局部变量。这句话是什么意思

  1. 局部变量colorStr 是一个在 Vue 组件的方法或计算属性中定义的局部变量,而不是组件的 data、props、computed 或 methods 中定义的全局变量或属性。
  2. 模板中不能使用:由于 colorStr 是一个局部变量,它只在定义它的函数或方法的作用域内有效。因此,你不能直接在模板中访问它,因为模板没有直接访问这些局部作用域的能力。
  • 也就是说👇:
  • 如果你想在模板中使用某个值,你应该确保该值是在组件的 data、props、computed 或 methods 中定义的,这样模板就可以访问它了。
<template>
<div class="my_div">
<img
src="https://scpic.chinaz.net/files/pic/pic9/202003/zzpic23514.jpg"
alt=""
/>
<p 
@click="btnFn"
:style="{background:colorS}"
>这是一个孤独可怜的狗</p>
</div>
</template>
<script>
export default {
data(){
return{
colorS:''
}
},
methods:{
// 随机颜色字符串
getRandColorStr(){
let r=Math.floor(Math.random()*256)
let g=Math.floor(Math.random()*256)
let b=Math.floor(Math.random()*256)
return `rgb(${r},${g},${b})`
},
btnFn(){
//在模板中不能使用colorStr,它是局部变量
let colorStr=this.getRandColorStr()
this.colorS=colorStr
}
}
};
</script>
<style>
.my_div {
width: 200px;
border: 1px solid black;
text-align: center;
float: left;
}
.my_div img {
width: 100%;
height: 200px;
}
</style>

2. 用子传父的方法

我试了,没意义

没必要用子传父

🍔组件—卖狗啦(父传子)

知识点

  1. 掌握组件的基础使用
  2. 掌握v-for的使用
  3. 掌握组件通信的使用

开始循环使用组件,分发数据到likeDog组件中

过程

  1. 拿到数据(本地固定编写 或者 ajax请求后台数据)
  2. 对组件用v-for
  3. 我需要把dogArr里具体的数据传到组件里啊
    • 组件通信——父传子

父组件App.vue

<template>
<div>
<likeDog 
v-for="(item,index) in dogArr" 
:key="index"
:dogObj="item"
></likeDog>
</div>
</template>
<script>
import likeDog from './components/likeDog.vue'
export default {
data(){
return{
dogArr:[
{
dogImgUrl:
"https://tse2-mm.cn.bing.net/th/id/OIP-C.vmMvnGQvSWUvnxV_MIO-1AHaHH?w=193&h=185&c=7&r=0&o=5&dpr=1.5&pid=1.7",
dogName: "博美",
},
{
dogImgUrl:
"https://tse2-mm.cn.bing.net/th/id/OIP-C.AuQ4kvFwC9R6_FhPcJy0uwHaEo?w=316&h=197&c=7&r=0&o=5&dpr=1.5&pid=1.7",
dogName: "泰迪",
},
{
dogImgUrl:
"https://tse2-mm.cn.bing.net/th/id/OIP-C.AuQ4kvFwC9R6_FhPcJy0uwHaEo?w=316&h=197&c=7&r=0&o=5&dpr=1.5&pid=1.7",
dogName: "金毛",
},
{
dogImgUrl:
"https://tse3-mm.cn.bing.net/th/id/OIP-C.IT2-niPVdy_BHh_C6VL9gwHaHa?w=172&h=180&c=7&r=0&o=5&dpr=1.5&pid=1.7",
dogName: "哈士奇",
},
{
dogImgUrl:
"https://tse2-mm.cn.bing.net/th/id/OIP-C.vmMvnGQvSWUvnxV_MIO-1AHaHH?w=193&h=185&c=7&r=0&o=5&dpr=1.5&pid=1.7",
dogName: "阿拉斯加",
},
{
dogImgUrl:
"https://tse2-mm.cn.bing.net/th/id/OIP-C.vmMvnGQvSWUvnxV_MIO-1AHaHH?w=193&h=185&c=7&r=0&o=5&dpr=1.5&pid=1.7",
dogName: "萨摩耶",
},
]
}
},
components:{
likeDog
}
};
</script>
<style>
</style>

子组件likeDog.vue

<template>
<div class="my_div">
<img
:src="dogObj.dogImgUrl"
alt=""
/>
<p 
@click="btnFn"
:style="{background:colorS}"
>{{ dogObj.dogName }}</p>
</div>
</template>
<script>
export default {
props:{
dogObj:Object
},
data(){
return{
colorS:''
}
},
methods:{
// 随机颜色字符串
getRandColorStr(){
let r=Math.floor(Math.random()*256)
let g=Math.floor(Math.random()*256)
let b=Math.floor(Math.random()*256)
return `rgb(${r},${g},${b})`
},
btnFn(){
//在模板中不能使用colorStr,它是局部变量
let colorStr=this.getRandColorStr()
this.colorS=colorStr
}
}
};
</script>
<style>
.my_div {
width: 200px;
border: 1px solid black;
text-align: center;
float: left;
}
.my_div img {
width: 100%;
height: 200px;
}
</style>

🍔组件—选择喜欢的狗(子传父)

知识点

  1. 掌握v-for
  2. 掌握组件通信——子传父
  3. 掌握自定义事件的使用
  4. 掌握事件传参的使用

过程

  1. 狗狗的名字绑定点击事件(刚刚已经绑过了)——传出去狗狗的名字
  2. 父组件中收集到名字放入数组
  3. 循环喜欢的狗的名字的数组

子组件likeDog.vue

<template>
<div class="my_div">
<img
:src="dogObj.dogImgUrl"
alt=""
/>
<p 
@click="btnFn"
:style="{background:colorS}"
>{{ dogObj.dogName }}</p>
</div>
</template>
<script>
export default {
props:{
dogObj:Object
},
data(){
return{
colorS:''
}
},
methods:{
// 随机颜色字符串
getRandColorStr(){
let r=Math.floor(Math.random()*256)
let g=Math.floor(Math.random()*256)
let b=Math.floor(Math.random()*256)
return `rgb(${r},${g},${b})`
},
btnFn(){
//在模板中不能使用colorStr,它是局部变量
let colorStr=this.getRandColorStr()
this.colorS=colorStr
// 选择喜欢的狗——把狗的名字传出去
this.$emit('dogEvent',this.dogObj.dogName)
}
}
};
</script>
<style>
.my_div {
width: 200px;
border: 1px solid black;
text-align: center;
float: left;
}
.my_div img {
width: 100%;
height: 200px;
}
</style>

父组件App.vue

<template>
<div>
<likeDog 
v-for="(item,index) in dogArr" 
:key="index"
:dogObj="item"
@dogEvent="dogNameFn"
></likeDog>
<ul>
<li v-for="(item,index) in loveDogList" :key="index">
{{ item }}
</li>
</ul>
</div>
</template>
<script>
import likeDog from './components/likeDog.vue'
export default {
data(){
return{
dogArr:[
{
dogImgUrl:
"https://tse2-mm.cn.bing.net/th/id/OIP-C.vmMvnGQvSWUvnxV_MIO-1AHaHH?w=193&h=185&c=7&r=0&o=5&dpr=1.5&pid=1.7",
dogName: "博美",
},
{
dogImgUrl:
"https://tse2-mm.cn.bing.net/th/id/OIP-C.AuQ4kvFwC9R6_FhPcJy0uwHaEo?w=316&h=197&c=7&r=0&o=5&dpr=1.5&pid=1.7",
dogName: "泰迪",
},
{
dogImgUrl:
"https://tse2-mm.cn.bing.net/th/id/OIP-C.AuQ4kvFwC9R6_FhPcJy0uwHaEo?w=316&h=197&c=7&r=0&o=5&dpr=1.5&pid=1.7",
dogName: "金毛",
},
{
dogImgUrl:
"https://tse3-mm.cn.bing.net/th/id/OIP-C.IT2-niPVdy_BHh_C6VL9gwHaHa?w=172&h=180&c=7&r=0&o=5&dpr=1.5&pid=1.7",
dogName: "哈士奇",
},
{
dogImgUrl:
"https://tse2-mm.cn.bing.net/th/id/OIP-C.vmMvnGQvSWUvnxV_MIO-1AHaHH?w=193&h=185&c=7&r=0&o=5&dpr=1.5&pid=1.7",
dogName: "阿拉斯加",
},
{
dogImgUrl:
"https://tse2-mm.cn.bing.net/th/id/OIP-C.vmMvnGQvSWUvnxV_MIO-1AHaHH?w=193&h=185&c=7&r=0&o=5&dpr=1.5&pid=1.7",
dogName: "萨摩耶",
},
],
loveDogList:[]
}
},
methods:{
dogNameFn(dogName){
this.loveDogList.push(dogName)
}
},
components:{
likeDog
}
};
</script>
<style>
</style>

🍔组件—卖完了

知识点

  1. 掌握v-for
  2. 掌握组件的创建和使用
  3. 掌握显示/隐藏标签的指令
  4. 掌握计算属性的使用

需求

  • 每一行,都封装成一个组件使用

过程

  1. 准备标签和样式
  2. 在父组件中,先把数据准备好,铺设页面上标签结构
  3. 封装tr组件,放入相应标签,引入到父组件中
  4. 然后循环数据,生成tr组件,父传子,传入数据展示页面
  5. 完成判断,如果数据为0,显示“卖光了”
  6. 定义计算属性,统计总数量

Tip:

!!! table标签比较特殊,里面只能写tbody、thead或者tr标签,不能使用组件标签

  • 所以我们将组件标签替换成,在tr里写一个is:""属性(意思是将tr标签替换成哪个组件)
  • (table和tr如此,select和option,ul和li,ol和li也如此)因为不能破坏原来的标签结构

Tip:

  1. 遍历数组里的对象,拿到的是对象的“内存地址”
  2. props传递赋值,传递的还是“内存地址”
  • 即——item变量和goodsObj变量指的都是数组里的那个对象
  • 对象是引用类型的
  • 子组件里修改对象里的属性(也就是v-model绑定的count属性)
  • 误区:(goodsObj={},这叫做修改变量本身的值,在子组件中是不允许的,因为父传子是单向数据流)
    • 但是改对象里的属性不算
  • 但是我在实战的时候,发现下面这样写,虽然理论上是我们想改属性,是对的,但是实战中还是会报错
  • 原因:
  • 解决办法:(在子组件中使用一个本地的 data 属性来存储计数值)

!!!!目前还有一个bug没有解决,那就是如果上面用一个新的count来存储的话,就无法在父组件里计算总和了(有点思路了,有时间再试一下)

父组件

<template>
<div>
<table>
<tr 
is="buyOver" 
v-for="(obj,index) in goodsArr" 
:key="index"
:goodsObj="obj"
@update:goodsObj="updateGoodsObj"
></tr>
</table>
<p>All Number:{{allCount}}</p>
</div>
</template>
<script>
import buyOver from './components/buyOver.vue'
export default {
data(){
return{
goodsArr: [
{
count: 1,
goodsName: "Watermelon"
}, {
count: 0,
goodsName: "Banana"
}, {
count: 0,
goodsName: "Orange"
}, {
count: 0,
goodsName: "Pineapple"
}, {
count: 0,
goodsName: "Strawberry"
}
]
}
},
computed:{
allCount(){
return this.goodsArr.reduce((sum,obj)=>{
return sum+=obj.count
},0)
}
},
methods:{
updateGoodsObj(obj){
this.count=obj.count
}
},
components:{
buyOver
}
};
</script>
<style>
</style>

子组件

<template>
<tr>
<td>
<input type="number" :value="goodsObj.count" @input="updateCount"/>
</td>
<td>
<span>{{goodsObj.goodsName}}</span>
</td>
<td>卖光了!!</td>
</tr>
</template>
<script>
export default {
props:{
goodsObj:Object
},
data(){
return{
}
},
methods: {  
updateCount() {  
// 触发一个自定义事件,将值传递给父组件  
this.$emit('update:goodsObj', { ...this.goodsObj });  
}  
}  
};
</script>
<style>
</style>

🍔组件—买点好吃的(父子通信)

知识点

  1. 对象引用关系的拷贝
  2. 事件绑定传参
  3. 数组的各种方法的运用

父组件App.vue

<template>
<div>
<p>商品清单如下:</p>
<ul>
<li v-for="(item,index) in foodArr" :key="index">
{{item.shopName}}——{{item.price}}元/份
</li>
</ul>
<p>请选择购买数量:</p>
<ul>
<li 
is="myFood"
v-for="(item,index) in foodArr"
:key="index"
:gName="item.shopName"
:gCount="item.count"
:ind="index"
@addEvent="addFn"
@cutEvent="cutFn"
></li>
</ul>
<div>
总价为:{{allPrice}}
</div>
</div>
</template>
<script >
import myFood from './components/myFood.vue'
export default{
data(){
return {
foodArr:[
{
"shopName": "可比克薯片",
"price": 5.5,
"count": 0
},
{
"shopName": "草莓酱",
"price": 3.5,
"count": 0
},
{
"shopName": "红烧肉",
"price": 55,
"count": 0
},
{
"shopName": "方便面",
"price": 12,
"count": 0
}
]
}
},
methods:{
addFn(index){
this.foodArr[index].count++
// 对象是引用类型,所有用到此对象的地方都会直接被影响(跟数组的v-for没关系)
},
cutFn(index){
let item=this.foodArr[index]
if(item.count>0){
item.count--
}
}
},
computed:{
allPrice(){
return this.foodArr.reduce((sum,item)=>{
return sum+=item.price*item.count
},0)
}
},
components:{
myFood
}
}
</script>
<style lang="scss" scoped>
</style>

子组件myFood.vue

<template>
<div>
{{gName}}
<button @click="addFn">+</button>
<span  >{{gCount}}</span>
<button @click="cutFn">-</button>
</div>
</template>
<script >
export default{
props:{
gName:String,
gCount:Number,
ind:Number
},
data(){
return{
}
},
methods:{
addFn(){
this.$emit('addEvent',this.ind)
},
cutFn(){
this.$emit('cutEvent',this.ind)
}
}
}
</script>
<style lang="scss" scoped>
</style>

🍔组件购物车….

知识点

  1. 掌握组件的创建和使用
  2. 掌握v-for的使用
  3. 掌握组件通讯
  4. 掌握全选和小选的做法
  5. 掌握计算属性的基础使用和完整写法
    • 用计算属性的完整写法实现全选和小选互相影响的特点

<template>
<div>
<table border="1" width="700" style="border-collapse: collapse">
<caption>
购物车
</caption>
<thead>
<tr>
<th><input type="checkbox" v-model="isAll" /> <span>全选</span></th>
<th>名称</th>
<th>价格</th>
<th>数量</th>
<th>小计</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr
is="MyTr"
v-for="(obj, index) in goodList"
:key="index"
:goodsObj="obj"
:ind="index"
@delEvent="delEFn"
></tr>
</tbody>
<tfoot>
<tr>
<td>合计:</td>
<td colspan="5">{{ allPrice }}</td>
</tr>
</tfoot>
</table>
</div>
</template>
<script>
// 目标: 完成购物车
// 1. App.vue里复制.md里的标签和数据
// 2. 创建MyTr.vue组件, 准备标签
// 3. 引入到App.vue使用组件显示
// 4. App.vue -> MyTr.vue 传入数据对象铺设页面
// 5. 计算属性完整写法, 来实现全选和小选互相影响效果
// 6. 实现+和-按钮的交互逻辑
// 7. 实现删除按钮功能
// 8. 统计总价
import MyTr from "./components/MyTr.vue"
export default {
data() {
return {
goodList: [
{
name: "诸葛亮",
price: 1000,
num: 1,
checked: false,
},
{
name: "蔡文姬",
price: 1500,
num: 1,
checked: false,
},
{
name: "妲己",
price: 2000,
num: 1,
checked: false,
},
{
name: "鲁班",
price: 2200,
num: 1,
checked: false,
},
],
};
},
methods: {
delEFn(index){
this.goodList.splice(index, 1)
}
},
computed: {
// 全选框的选中状态
// isAll(){
//   // 全选状态-统计所有小选框选中状态, 而得来
//   // every() - 判断数组里每个值有一个不符合状态就直接返回false
//   return this.goodList.every(obj => {
//    return obj.checked === true
//   })
//   // 有一个人没选中, 判断条件为false, 最终every也为false, 正好isAll为false, 全选也没选中
// }
// 完整写法: 当页面或者其他地方给计算属性, 赋予一个值
isAll: {
set(val) {
// 当前案例: 用户点击全选框
// 全选选中, val就是true, 全选未选中, val就是false
this.goodList.forEach(obj => {
obj.checked = val;
})
},
get() {
if (this.goodList.length === 0) {
return false;
}
return this.goodList.every((obj) => {
return obj.checked === true;
});
},
},
// 统计总价
allPrice(){
// 你选中了, 才统计
return this.goodList.reduce((sum, obj) => {
if (obj.checked === true) {
sum += obj.price * obj.num
}
return sum; // 一定要给下一次sum的初始值return值进行累加
}, 0)
}
},
components: {
MyTr,
},
};
</script>
<style>
</style>

<template>
<tr>
<td>
<!-- (goodsObj={},这叫做修改变量本身的值,在子组件中是不允许的,因为父传子是单向数据流) -->
<!-- 但是改对象里的属性不算 -->
<input type="checkbox" v-model="goodsObj.checked">
</td>
<td>
<span>{{ goodsObj.name }}</span>
</td>
<td>
<span>{{ goodsObj.price }}</span>
</td>
<td>
<button @click="subFn">-</button>
<span>{{ goodsObj.num }}</span>
<button @click="addFn">+</button>
</td>
<td>
<span>{{ goodsObj.price * goodsObj.num  }}</span>
</td>
<td>
<button @click="delFn">删除</button>
</td>
</tr>
</template>
<script>
export default {
props: {
goodsObj: Object,
ind: Number, // 索引
},
methods: {
// +
addFn(){
this.goodsObj.num++
},
// -
subFn(){
if (this.goodsObj.num > 1) {
this.goodsObj.num--
}
},
// 删除点击事件
delFn(){
this.$emit('delEvent', this.ind)
}
}
}
</script>
<style>
</style>

🥝 全选小选交互(父子通信)

<template>  
<div>  
<myTr :value="isAllSelected" @change="handleAllChange">全选</myTr>  
<div v-for="(item, index) in items" :key="index">  
<myTr :value="item.selected" @change="handleItemChange(index, $event)">  
{{ item.name }}  
</myTr>  
</div>  
</div>  
</template>  
<script>  
import myTr from './components/myTr.vue';
export default {  
name: 'SelectAllCheckbox',  
components: {  
myTr
},  
data() {  
return {  
isAllSelected: false,  
items: [  
{ name: 'Item 1', selected: false },  
{ name: 'Item 2', selected: false },  
{ name: 'Item 3', selected: false }  
]  
};  
},  
methods: {  
handleAllChange(value) {  
this.isAllSelected = value;  
this.updateItemSelections(value);  
},  
handleItemChange(index, value) {  
this.items[index].selected = value;  
this.updateAllSelection();  
},  
updateItemSelections(value) {  
this.items.forEach(item => {  
item.selected = value;  
});  
},  
updateAllSelection() {  
this.isAllSelected = this.items.every(item => item.selected);  
}  
}  
};  
</script>
<template>  
<label>  
<input type="checkbox" :checked="value" @change="onChange" />  
<slot></slot>  
</label>  
</template>  
<script>  
export default {  
name: 'myTr',  
props: {  
value: {  
type: Boolean,  
default: false  
}  
},  
methods: {  
onChange(event) {  
this.$emit('change', event.target.checked);  
}  
}  
};  
</script>

🍔 做数学题

知识点

  1. 熟悉创建组件
  2. 组件通信
  3. 父子关系传值的口诀
  4. v-for
  5. 对象的引用关系
  6. 单向数据流

重点

  • 获取结果,点击提交,计算结果,影响isOk状态
  • !!!!(组件只负责数据显示,逻辑全回传给App.vue页面)

App.vue

<template>
<div id="app">
<h2>测试题</h2>
<SubjectObj
v-for="(obj,index) in arr"
:key="index"
:one="obj.num1"
:two="obj.num2"
:index="index"
@celBtn="celFn"
></SubjectObj>
<div>
<FlagObj
v-for="(obj,index) in arr"
:key="index"
:status="obj.isOk"
:index="index"
></FlagObj>
</div>
</div>
</template>
<script>
import FlagObj from './components/FlagObj'
import SubjectObj from './components/SubjectObj'
export default {
data(){
return{
arr:[
{
num1:Math.floor(Math.random()*10),
num2:Math.floor(Math.random()*10),
isOk:'unEnd'
},
{
num1:Math.floor(Math.random()*10),
num2:Math.floor(Math.random()*10),
isOk:'unEnd'
},
{
num1:Math.floor(Math.random()*10),
num2:Math.floor(Math.random()*10),
isOk:'unEnd'
},
{
num1:Math.floor(Math.random()*10),
num2:Math.floor(Math.random()*10),
isOk:'unEnd'
},
{
num1:Math.floor(Math.random()*10),
num2:Math.floor(Math.random()*10),
isOk:'unEnd'
}
]
}
},
methods:{
celFn(index,result){
let obj=this.arr[index]
if(obj.num1+obj.num2===result){
obj.isOk="yes"
}else{
obj.isOk="no"
}
}
},
components:{
FlagObj,
SubjectObj
},
};
</script>
<style>
body {
background-color: #eee;
}
#app {
background-color: #fff;
width: 500px;
margin: 50px auto;
box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.5);
padding: 2em;
}
</style>

SubjectObj.vue

<template>
<div class="subject">
<span>{{ one }}</span>
<span>+</span>
<span>{{ two }}</span>
<span>=</span>
<input type="number" v-model.number="result" :disabled="isCheck"/>
<button @click="celFn" :disabled="isCheck">提交</button>
</div>
</template>
<script>
export default {
props:['one','two','index'],
data(){
return {
result:'  ',
isCheck:false
}
},
methods:{
celFn(){
this.$emit('celBtn',this.index,this.result),
this.isCheck=true
}
}
}
</script>
<style scoped>
.subject {
margin: 5px;
padding: 5px;
font-size: 20px;
}
.subject span {
display: inline-block;
text-align: center;
width: 20px;
}
.subject input {
width: 50px;
height: 20px;
}
</style>

FlagObj.vue

<template>
<span :class="{undo:status==='unEnd',right:status==='yes',error:status==='no'}">
{{index+1}}:
{{ status==='unEnd'?'未完成': status==='yes'?'正确': '错误' }}
</span>
</template>
<script>
export default {
props:['status','index']
};
</script>
<style scoped>
.right {
color: green;
}
.error {
color: red;
}
.undo {
color: #ccc;
}
</style>

🥝 页面输入框获取焦点—(全局自定义指令)

vue的dom元素渲染是异步更新的,所以当focus执行的时候,imp还并没有渲染到,所以无效

<input v-if="isEdit" ref="inp"/>
handleClick () {
// 双击后切换到显示状态
this.isEdit = true
// 也就是当我们切换到显示状态时,dom并未渲染完,此时你立刻获取焦点是不行的
this.$refs.inp.focus()
// 所以正确写法是,在这个外面包一个$nextTick
this.$nextTick(() => {
//立刻获取焦点
this.$refs.inp.focus()
})
}

问题

但是现在的问题是,你之后写的页面复杂了之后,就有可能有很多个地方都需要获取焦点,那么你这样一个地方获取一次就比较麻烦

解决

所以我们决定采用——全局自定义指令的方法

  • 写到main.js里
// 页面上把自定义指令写上去——v-focus
<input v-if="isEdit" v-focus ref="inp"/>
// 封装全局指令 focus
Vue.directive('focus', {
// 钩子函数👇
// 指令所在的dom元素,被插入到页面中时触发
//inserted有两个参数el和binding,但是我们这个功能只要el就可以了
inserted (el) {
el.focus()
}
})

🥝编辑回显功能——(父子通信)

上面的学生管理系统,也有编辑回显的例子,但是,那个不是父子通信

下面写到的是父子通信的编辑回显功能:

回显+编辑

  • 回显的标签信息是父组件传递过来的
  • 回显后也要可以在子组件中被修改,再传回给父组件
  1. 回显(父传子)

父组件中👇

<MyTag v-model="tempText"></MyTag>
data () {
return {
// 测试组件功能的临时数据
tempText: '水杯',
tempText2: '钢笔',
}
}

子组件中👇

      <input
:value="value"
/>
<div>
{{ value }}
</div>
props: {
value: String
},
  1. 编辑——(处理回车事件)(子传父)

子组件中👇

$emit 传递的是输入框的内容

      <input
@keyup.enter="handleEnter"
/>
handleEnter (e) {
// 非空处理
if (e.target.value.trim() === '') return alert('标签内容不能为空')
// 子传父,将回车时,[输入框的内容] 提交给父组件更新
// 由于父组件是v-model,触发事件,需要触发 input 事件
this.$emit('input', e.target.value)
// 提交完成,关闭输入状态
this.isEdit = false
}

父组件中👇

由于父组件已经写成v-model了,那就已经双向了
子传父达成  

🥝 商品列表——插槽

定制————具名插槽,作用域插槽

Vue组件封装案例——商品列表 – 掘金 (juejin.cn)

🍔 一级路由—切换界面

知识点

  1. 掌握创建组件
  2. 掌握vue-router路由使用
  3. 掌握router-link和router-view组件使用
  4. 掌握vue-router的5+2步骤
  5. 掌握ul的路由切换,知道如何匹配规则和挂载

App.vue

<template>
<div>
<div>
<router-link to="/buyPage">订单</router-link>
<router-link to="/categoryPage">分类</router-link>
<router-link to="/homePage">首页</router-link>
<router-link to="/myPage">我的</router-link>
</div>
<div>
<h2>显示路由切换的页面👇</h2>
<router-view></router-view>
</div>
</div>
</template>
<script>
</script>
<style lang="scss" scoped>
</style>

main.js

import Vue from 'vue'
import App from './App.vue'
import buyPage from '@/views/buyPage.vue'
import categoryPage from '@/views/categoryPage'
import homePage from '@/views/homePage.vue'
import myPage from '@/views/myPage.vue'
import VueRouter from 'vue-router'
Vue.config.productionTip = false
Vue.use(VueRouter)
const router = new VueRouter({
routes:[
{path:'/',redirect:'/homePage'},
{path:'/buyPage',component:buyPage},
{path:'/categoryPage',component:categoryPage},
{path:'/homePage',component:homePage},
{path:'/myPage',component:myPage}
]
})
new Vue({
render: h => h(App),
router
}).$mount('#app')

myPage.vue(其他几个组件同理)

<template>
<div>
<h3>这里是个人页面</h3>
</div>
</template>
<script>
</script>
<style lang="scss" scoped>
</style>

🍔 二级路由嵌套—children

路由规则里配置children——> 重点:在哪个上级路由下
main.js

import Vue from 'vue'
import App from './App.vue'
import buyPage from '@/views/buyPage.vue'
import categoryPage from '@/views/categoryPage'
import homePage from '@/views/homePage.vue'
import myPage from '@/views/myPage.vue'
import MySport from '@/Second/MySport'
import YourSport from '@/Second/YourSport'
import SportHome from '@/Second/SportHome'
import VueRouter from 'vue-router'
Vue.config.productionTip = false
Vue.use(VueRouter)
const router = new VueRouter({
routes:[
{path:'/',redirect:'/homePage'},
{path:'/buyPage',component:buyPage},
{path:'/categoryPage',component:categoryPage},
{
path:'/homePage',
component:homePage,
redirect:'/homePage/SportHome',
children:[
{path:'MySport',component:MySport},
{path:'YourSport',component:YourSport},
{path:'SportHome',component:SportHome}
]
},
{path:'/myPage',component:myPage}
]
})
new Vue({
render: h => h(App),
router
}).$mount('#app')

homePage.vue

<template>
<div>
<h3>这里是首页</h3>
<hr>
<router-link to="/homePage/SportHome">体育首页</router-link>
<br>
<router-link to="/homePage/MySport">国内</router-link>
<br>
<router-link to="/homePage/YourSport">国外 </router-link>
<h3>下面是二级路由👇</h3>
<router-view></router-view>
</div>
</template>
<script>
</script>
<style scoped>
</style>

这有个小错误,就是Second文件夹应该是在views文件夹里的

🍔 三级路由嵌套

三级路由嵌套跟二级路由是一个道理——还是在里面再写children

随机显示按钮切换路由👇

 methods: {
btnFn(){
let arr = ['/contacts/all_contacts', '/contacts/alice', '/contacts/bob']
let path = arr[Math.floor(Math.random() * arr.length)]
// 写一个if避免报错,意思是如果你随机出来的这个页面跟你当前页面不一样,再跳转
if (this.$route.path !== path) {
this.$router.push(path)
}
}
}

🥝 Vuex—state和mutation

功能

  1. 父子组件数据共享(store仓库里的state提供)
  2. mutations同步修改父子组件数据
  3. input双向绑定
    App.vue
<template>
<div class="box">
数据:{{ $store.state.count }}
<br>
<input :value="count" @input="handleCount" type="text">
<SonOne></SonOne>
</div>
</template>
<script>
import SonOne from './components/SonOne'
import { mapState } from 'vuex'
export default {
computed: {
...mapState(['count'])
},
methods: {
handleCount (e) {
const num = +e.target.value
this.$store.commit('handleFn', num)
}
},
components: {
SonOne
}
}
</script>
<style >
.box{
background:blanchedalmond;
width: 500px;
height: 200px;
}
</style>

store/index.js

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
count: 100
},
mutations: {
addCount (state, n) {
state.count += n
},
handleFn (state, newCount) {
state.count = newCount
}
}
})

SonOne.vue

<template>
<div>
值:{{ $store.state.count }}
<br>
<button @click="AddFn(10)">值加10</button>
<button @click="AddFn(5)">值加5</button>
<button @click="AddFn(1)">值加1</button>
</div>
</template>
<script >
export default {
methods: {
AddFn (n) {
this.$store.commit('addCount', n)
}
}
}
</script>
<style>
div{
background: greenyellow;
margin: 10px;
}
</style>

原文链接:https://juejin.cn/post/7349119682002223104 作者:phy_winter