Vue基础教程(94)表单输入绑定之复选框:深度解析Vue复选框绑定:从“相亲角”到“甜蜜联姻”的奇幻之旅

第一章:初识复选框——从前有个“多情”的输入框

还记得那些年我们追过的JavaScript吗?当遇到复选框时,我们得这样苦口婆心地劝说:

// 传统的JS方式,累死个人!
const checkboxes = document.querySelectorAll('input[type="checkbox"]');
let selectedValues = [];

checkboxes.forEach(checkbox => {
  checkbox.addEventListener('change', function() {
    if (this.checked) {
      selectedValues.push(this.value);
    } else {
      const index = selectedValues.indexOf(this.value);
      if (index > -1) {
        selectedValues.splice(index, 1);
      }
    }
    console.log('当前选中的值:', selectedValues);
  });
});

光是写这段代码,我就已经想念Vue了!来看看Vue是怎么“宠幸”我们的:

<template>
  <div>
    <input type="checkbox" v-model="isChecked"> 同意协议
    <p>当前状态:{{ isChecked ? '已同意' : '未同意' }}</p>
  </div>
</template>
<script>
export default {
  data() {
    return {
      isChecked: false
    }
  }
}
</script>

v-model就像个贴心的红娘,在复选框和数据之间牵线搭桥。当你在页面上勾选时,isChecked自动变成true;取消勾选时,它又乖巧地变回false

第二章:单个复选框——单身贵族的自我修养

单个复选框虽然简单,但人家可是有自己的小脾气的!

场景一:注册协议同意框

<template>
  <div class="register-form">
    <h3>注册新用户</h3>
    
    <div class="agreement">
      <input 
        type="checkbox" 
        id="agree"
        v-model="formData.agreed"
        true-value="yes"
        false-value="no"
      >
      <label for="agree">我已阅读并同意《用户协议》</label>
    </div>
    
    <button :disabled="!formData.agreed" @click="handleSubmit">
      立即注册
    </button>
    
    <p>协议状态:{{ formData.agreed }}</p>
    <p>按钮是否可用:{{ !formData.agreed ? '不可用' : '可用' }}</p>
  </div>
</template>
<script>
export default {
  data() {
    return {
      formData: {
        agreed: 'no' // 默认不同意
      }
    }
  },
  methods: {
    handleSubmit() {
      if (this.formData.agreed === 'yes') {
        alert('注册成功!');
        // 这里可以提交表单
      }
    }
  }
}
</script>

知识点敲黑板:

  • true-valuefalse-value就像给复选框的两种状态起了个小名
  • 默认情况下,选中时值为true,未选中为false
  • 使用自定义值后,选中的值变成"yes",未选中变成"no"

第三章:多个复选框——后宫选妃大戏开场

当多个复选框同时出现,那场面就像皇帝选妃——你得知道选了哪些,没选哪些!

场景二:兴趣选择(多选)

<template>
  <div class="interest-select">
    <h3>请选择你的兴趣爱好(可多选):</h3>
    
    <div class="interest-options">
      <label v-for="interest in interests" :key="interest.value">
        <input 
          type="checkbox" 
          :value="interest.value" 
          v-model="selectedInterests"
        >
        {{ interest.label }}
      </label>
    </div>
    
    <div class="selection-result">
      <h4>你选择了:</h4>
      <ul>
        <li v-for="selected in selectedInterests" :key="selected">
          {{ getInterestLabel(selected) }}
        </li>
      </ul>
      <p v-if="selectedInterests.length === 0">还没有选择任何兴趣</p>
    </div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      interests: [
        { value: 'reading', label: '阅读' },
        { value: 'music', label: '音乐' },
        { value: 'sports', label: '运动' },
        { value: 'travel', label: '旅行' },
        { value: 'gaming', label: '游戏' }
      ],
      selectedInterests: [] // 注意:这里是数组!
    }
  },
  methods: {
    getInterestLabel(value) {
      const interest = this.interests.find(item => item.value === value);
      return interest ? interest.label : '';
    }
  }
}
</script>

重要提醒:多个复选框绑定时,v-model必须绑定到数组! 如果你不小心绑定到字符串或者布尔值,Vue会默默哭泣——它不知道该把多个值往哪里放。

第四章:实战进阶——超市购物车案例

理论说再多,不如来个实战!让我们模拟一个超市购物车:

<template>
  <div class="shopping-cart">
    <h2>🛒 超市购物车</h2>
    
    <div class="products">
      <div 
        v-for="product in products" 
        :key="product.id"
        class="product-item"
        :class="{ selected: isProductSelected(product.id) }"
      >
        <label class="product-select">
          <input 
            type="checkbox" 
            :value="product.id" 
            v-model="selectedProducts"
            @change="updateTotal"
          >
          {{ product.name }} - ¥{{ product.price }}
        </label>
        
        <div class="quantity" v-if="isProductSelected(product.id)">
          数量:
          <input 
            type="number" 
            v-model.number="product.quantity" 
            min="1"
            @change="updateTotal"
          >
        </div>
      </div>
    </div>
    
    <div class="cart-summary">
      <h3>购物车汇总</h3>
      <p>已选商品:{{ selectedProducts.length }} 件</p>
      <p>总金额:¥{{ totalAmount }}</p>
      <p v-if="selectedProducts.length > 0">
        最贵的商品:{{ mostExpensiveProduct }}
      </p>
      <button 
        :disabled="selectedProducts.length === 0"
        @click="checkout"
      >
        结算({{ selectedProducts.length }}件商品)
      </button>
    </div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      products: [
        { id: 1, name: '苹果', price: 8, quantity: 1 },
        { id: 2, name: '香蕉', price: 5, quantity: 1 },
        { id: 3, name: '橙子', price: 6, quantity: 1 },
        { id: 4, name: '葡萄', price: 12, quantity: 1 }
      ],
      selectedProducts: [],
      totalAmount: 0
    }
  },
  computed: {
    mostExpensiveProduct() {
      if (this.selectedProducts.length === 0) return '';
      
      const selectedProductDetails = this.products.filter(
        product => this.selectedProducts.includes(product.id)
      );
      
      const mostExpensive = selectedProductDetails.reduce((max, product) => {
        return product.price > max.price ? product : max;
      });
      
      return mostExpensive.name;
    }
  },
  methods: {
    isProductSelected(productId) {
      return this.selectedProducts.includes(productId);
    },
    
    updateTotal() {
      this.totalAmount = this.selectedProducts.reduce((total, productId) => {
        const product = this.products.find(p => p.id === productId);
        return total + (product.price * product.quantity);
      }, 0);
    },
    
    checkout() {
      const selectedItems = this.products.filter(
        product => this.selectedProducts.includes(product.id)
      );
      
      alert(`结算成功!\n商品:${selectedItems.map(item => 
        `${item.name}×${item.quantity}`
      ).join(',')}\n总金额:¥${this.totalAmount}`);
    }
  }
}
</script>
<style scoped>
.product-item {
  padding: 10px;
  margin: 5px 0;
  border: 1px solid #ddd;
  border-radius: 5px;
}

.product-item.selected {
  background-color: #f0f8ff;
  border-color: #1890ff;
}

.quantity {
  margin-top: 5px;
}

.quantity input {
  width: 60px;
  padding: 2px 5px;
}
</style>

这个案例涵盖了:

  • 复选框选择商品
  • 动态显示数量输入框
  • 实时计算总金额
  • 查找最贵商品
  • 结算功能

第五章:高级玩法——自定义三态复选框

有时候,我们需要一个"半选"状态,比如全选功能中的 indeterminate 状态:

<template>
  <div class="tri-state-checkbox">
    <h3>权限设置(三态演示)</h3>
    
    <div class="permission-group">
      <label class="main-permission">
        <input 
          type="checkbox" 
          ref="mainCheckbox"
          v-model="mainState"
          @change="handleMainChange"
        >
        系统管理权限
      </label>
      
      <div class="sub-permissions">
        <label v-for="permission in permissions" :key="permission.id">
          <input 
            type="checkbox" 
            :value="permission.id" 
            v-model="selectedPermissions"
            @change="updateMainState"
          >
          {{ permission.name }}
        </label>
      </div>
    </div>
    
    <p>主复选框状态:{{ mainState }}</p>
    <p>选中的子权限:{{ selectedPermissions }}</p>
  </div>
</template>
<script>
export default {
  data() {
    return {
      permissions: [
        { id: 'user_manage', name: '用户管理' },
        { id: 'role_manage', name: '角色管理' },
        { id: 'log_view', name: '日志查看' }
      ],
      selectedPermissions: [],
      mainState: false
    }
  },
  mounted() {
    this.updateMainState();
  },
  methods: {
    updateMainState() {
      const total = this.permissions.length;
      const selected = this.selectedPermissions.length;
      
      if (selected === 0) {
        this.mainState = false;
        this.$refs.mainCheckbox.indeterminate = false;
      } else if (selected === total) {
        this.mainState = true;
        this.$refs.mainCheckbox.indeterminate = false;
      } else {
        this.mainState = false;
        this.$refs.mainCheckbox.indeterminate = true;
      }
    },
    
    handleMainChange() {
      if (this.mainState) {
        // 选中所有
        this.selectedPermissions = this.permissions.map(p => p.id);
      } else {
        // 取消所有
        this.selectedPermissions = [];
      }
    }
  }
}
</script>

三态复选框的精髓:

  • indeterminate属性控制"半选"状态
  • 通过计算选中数量来决定主复选框状态
  • 主复选框控制所有子复选框

第六章:避坑指南——复选框的那些"坑"

  1. 数组初始化问题
// ❌ 错误做法
data() {
  return {
    selectedItems: '' // 应该用数组!
  }
}

// ✅ 正确做法
data() {
  return {
    selectedItems: [] // 多个复选框必须用数组
  }
}
  1. v-model值类型问题
<!-- ❌ 值类型不一致 -->
<input type="checkbox" value="1" v-model="selectedIds">
<input type="checkbox" value="2" v-model="selectedIds">

<!-- ✅ 保持值类型一致 -->
<input type="checkbox" :value="1" v-model="selectedIds">
<input type="checkbox" :value="2" v-model="selectedIds">
  1. 动态生成复选框的key问题
<!-- ❌ 缺少key -->
<label v-for="item in list">
  <input type="checkbox" :value="item.id" v-model="selected">
  {{ item.name }}
</label>
<!-- ✅ 添加key -->
<label v-for="item in list" :key="item.id">
  <input type="checkbox" :value="item.id" v-model="selected">
  {{ item.name }}
</label>

第七章:完整示例大放送

最后,给大家一个完整的、可运行的示例:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>Vue复选框绑定完整示例</title>
  <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
  <style>
    body { font-family: 'Microsoft YaHei', sans-serif; padding: 20px; }
    .container { max-width: 800px; margin: 0 auto; }
    .section { margin-bottom: 30px; padding: 20px; border: 1px solid #e1e1e1; border-radius: 8px; }
    .checkbox-group label { display: block; margin: 8px 0; cursor: pointer; }
    .selected { background: #e3f2fd; padding: 10px; border-radius: 4px; margin-top: 10px; }
    button { padding: 10px 20px; background: #2196f3; color: white; border: none; border-radius: 4px; cursor: pointer; }
    button:disabled { background: #ccc; cursor: not-allowed; }
  </style>
</head>
<body>
  <div id="app" class="container">
    <h1>🎯 Vue复选框绑定实战大全</h1>
    
    <!-- 单个复选框示例 -->
    <div class="section">
      <h2>1. 单个复选框 - 协议同意</h2>
      <label>
        <input type="checkbox" v-model="singleCheckbox"> 
        我同意《霸王条款》
      </label>
      <p>状态:{{ singleCheckbox ? '已同意' : '未同意' }}</p>
    </div>
    
    <!-- 多个复选框示例 -->
    <div class="section">
      <h2>2. 多个复选框 - 技能选择</h2>
      <div class="checkbox-group">
        <label v-for="skill in skills" :key="skill.id">
          <input type="checkbox" :value="skill.id" v-model="selectedSkills">
          {{ skill.name }}
        </label>
      </div>
      <div class="selected">
        <p>已选技能:</p>
        <ul>
          <li v-for="skillId in selectedSkills" :key="skillId">
            {{ getSkillName(skillId) }}
          </li>
        </ul>
        <p v-if="selectedSkills.length === 0">还没有选择任何技能</p>
      </div>
    </div>
    
    <!-- 自定义值示例 -->
    <div class="section">
      <h2>3. 自定义值 - 订阅设置</h2>
      <label>
        <input 
          type="checkbox" 
          v-model="subscription" 
          true-value="subscribed"
          false-value="unsubscribed"
        > 
        接收促销邮件
      </label>
      <p>订阅状态:{{ subscription }}</p>
    </div>
    
    <!-- 操作按钮 -->
    <div class="section">
      <button @click="submitForm">提交表单</button>
      <button @click="resetForm" style="margin-left: 10px; background: #ff9800;">重置</button>
    </div>
    
    <!-- 结果显示 -->
    <div class="section" v-if="submitted">
      <h2>📋 提交结果</h2>
      <p>协议同意:{{ submissionData.agreed ? '是' : '否' }}</p>
      <p>选择技能:{{ submissionData.skills.join('、') }}</p>
      <p>订阅状态:{{ submissionData.subscription }}</p>
    </div>
  </div>
  <script>
    const { createApp, ref } = Vue;
    
    createApp({
      setup() {
        // 单个复选框
        const singleCheckbox = ref(false);
        
        // 多个复选框
        const skills = ref([
          { id: 1, name: 'Vue.js' },
          { id: 2, name: 'React' },
          { id: 3, name: 'Angular' },
          { id: 4, name: 'Node.js' }
        ]);
        const selectedSkills = ref([]);
        
        // 自定义值
        const subscription = ref('unsubscribed');
        
        // 提交状态
        const submitted = ref(false);
        const submissionData = ref({});
        
        const getSkillName = (skillId) => {
          const skill = skills.value.find(s => s.id === skillId);
          return skill ? skill.name : '未知技能';
        };
        
        const submitForm = () => {
          submissionData.value = {
            agreed: singleCheckbox.value,
            skills: selectedSkills.value.map(getSkillName),
            subscription: subscription.value
          };
          submitted.value = true;
          alert('表单提交成功!请查看提交结果。');
        };
        
        const resetForm = () => {
          singleCheckbox.value = false;
          selectedSkills.value = [];
          subscription.value = 'unsubscribed';
          submitted.value = false;
        };
        
        return {
          singleCheckbox,
          skills,
          selectedSkills,
          subscription,
          submitted,
          submissionData,
          getSkillName,
          submitForm,
          resetForm
        };
      }
    }).mount('#app');
  </script>
</body>
</html>

结语:复选框与Vue的"婚后生活"

通过这次深度探索,相信你已经掌握了Vue复选框绑定的精髓。从简单的单个复选框到复杂的多选场景,再到高级的三态控制,v-model就像个贴心的管家,帮你处理所有的状态同步。

记住:单个复选框绑定到布尔值,多个复选框绑定到数组,这是避免踩坑的关键!

现在,去和Vue的复选框开启你们的"甜蜜联姻"吧!如果遇到问题,记得回来看这份"婚姻指南"哦~

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

值引力

持续创作,多谢支持!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值