Vue2 搜索+checkbox 组件化开发

1. 实现功能

1.1. 实现搜索和过滤功能

1.1.1. App.vue 使用组件

<template>
  <Demo />
</template>


<script>
import Demo from "./components/Demo";

export default {
  name: "App",
  components: {
    Demo,
  },
};
</script>

1.1.2. 🌟实现搜input + 按钮搜索功能

setScore去设置响应式数据searchedScore

setFilterRegular去设置响应式数据filterRegular用来控制类的添加和删除(实现样式的切换)

<template>
<div class="search-component">
    <div class="input-box">
        <input 
        type="number" 
        placeholder="请输入分数"
        @input="setScore"
        :value="searchedScore"
        />
    </div>
    <!-- 筛选xx分 以上/以下 的人 -->
    <!-- 被选中的时候加上类active实现特殊样式 -->
    <button 
    @click="setFilterRegular('up')"
    :className="['btn', { active: filterRegular === 'up'}]"
    >
        以上
    </button>
    <button 
    @click="setFilterRegular('down')"
    :className="['btn', { active: filterRegular === 'down'}]"
    >
        以下
    </button>
    <div>
    
</div>
</template>


<script>
export default {
  name: "Demo",
  data() {
    return {
      searchedScore: 0,
      filterRegular: "up",
    };
  },
  methods: {
    // 拿到输入框的值
    setScore(e) {
      this.searchedScore = Number(e.target.value);
    },
    // 设置filterRegular
    setFilterRegular(type) {
      this.filterRegular = type;
    },
  },
};
</script>


<style lang="scss" acoped>
.active {
  background-color: #000;
  color: #fff;
}
</style>

1.1.3. 数据

// 实现:
// 通过分数过滤出对应的学生,并在menu中进行展示
export default [
    {
        id: 1,
        name: '小红',
        score: 98
    },
    {
        id: 2,
        name: '小红1',
        score: 88
    },
    {
        id: 3,
        name: '小红2',
        score: 78
    },
    {
        id: 4,
        name: '小红3',
        score: 68
    },
]

1.1.4. 🌟实现menu功能

渲染视图 v-show="menuShow" v-for="item of menuList"展示的是顾虑后的数据

设置响应式数据menuShow menuList

根据输入框的值this.searchedScore + 以上/以下this.filterRegular 过滤数据

如果有数据,就要展示meun

在输入框输入 和 点击button时,都要过滤数据

<template>
<div class="search-component">
    <div class="input-box">
        <input 
        type="number" 
        placeholder="请输入分数"
        @input="setScore"
        :value="searchedScore"
        />
    </div>
    <!-- 筛选xx分 以上/以下 的人 -->
    <!-- 被选中的时候加上类active实现特殊样式 -->
    <button 
    @click="setFilterRegular('up')"
    :className="['btn', { active: filterRegular === 'up'}]"
    >
        以上
    </button>
    <button 
    @click="setFilterRegular('down')"
    :className="['btn', { active: filterRegular === 'down'}]"
    >
        以下
    </button>
    <div>
    <div class="mune" v-show="menuShow">
        <ul>
            <li 
            v-for="item of menuList"
            :key="item.id"
            >
            <input type="checkbox" />
            <span>{{ item.name }}</span>
            <span>{{ item.score }}</span>
            </li>
        </ul>
    </div>
    
</div>
</template>


<script>
import studentData from "../data/student";

export default {
  name: "Demo",
  data() {
    return {
      searchedScore: 0,
      filterRegular: "up",
      menuShow: false,
      menuList: [],
    };
  },
  methods: {
    // 设置值
    setScore(e) {
      // 拿到输入框的值
      this.searchedScore = Number(e.target.value);
      // 输入时 -> 调用过滤数据的函数 -> 筛选出展示在menu中的数据
      this.filterStudents();
    },
    // 设置filterRegular
    setFilterRegular(type) {
      this.filterRegular = type;
      // 切换时 -> 调用过滤数据的函数 -> 筛选出展示在menu中的数据
      this.filterStudents();
    },
    // 根据输入框的值this.searchedScore + 以上/以下this.filterRegular 过滤数据
    filterStudents() {
      switch (this.filterRegular) {
        case "up":
          // filter返回一个新数组
          this.menuList = studentData.filter(
            (item) => item.score >= this.searchedScore
          );
          break;
        case "down":
          this.menuList = studentData.filter(
            (item) => item.score < this.searchedScore
          );
          break;
        default:
          break;
      }

      // 如果有数据 就要展示meun
      this.menuShow = this.menuList.length > 0;// 用这一句话实现menuShow是true 还是 false的判断
    },
  },
};
</script>


<style lang="scss" acoped>
.active {
  background-color: #000;
  color: #fff;
}
</style>

1.2. 🌟列表联动与选中状态保持

点击input触发事件setChecked,传入当前遍历的item

-> 创建另外一个数据checkedList(选中checkbox的数据并进行展示)

-> 往checkedList中放数据之前,要判断是否已经存在这个数据getChecked

-> setChecked设置数据: 调用方法getChecked来判断是否存在这个数据 存在(要从checkedList中删除这个数据 --> 也就是 用filter拿出除了当前id的其他数据) 不存在(把当前的数据push进checkedList)

-> 如果数据存在于checkedList,menu中的数据要被选中 :checked="getChecked(item.id)"

-> 写checked-list的模版

<template>
<div class="search-component">
    <div class="input-box">
        <input 
        type="number" 
        placeholder="请输入分数"
        @input="setScore"
        :value="searchedScore"
        />
    </div>
    <!-- 筛选xx分 以上/以下 的人 -->
    <!-- 被选中的时候加上类active实现特殊样式 -->
    <button 
    @click="setFilterRegular('up')"
    :className="['btn', { active: filterRegular === 'up'}]"
    >
        以上
    </button>
    <button 
    @click="setFilterRegular('down')"
    :className="['btn', { active: filterRegular === 'down'}]"
    >
        以下
    </button>
    <div>
    <div class="mune" v-show="menuShow">
        <ul>
            <li 
            v-for="item of menuList"
            :key="item.id"
            >
            <input type="checkbox" 
            @click="setChecked(item)"
            :checked="getChecked(item.id)"
            />
            <span>{{ item.name }}</span>
            <span>{{ item.score }}</span>
            </li>
        </ul>
    </div>
    <div class="checked-list">
         <ul>
            <li 
            v-for="item of checkedList"
            :key="item.id"
            >
            <input type="checkbox" 
            @click="setChecked(item)"
            />
            <span>{{ item.name }}</span>
            <span>{{ item.score }}</span>
            </li>
        </ul>
    </div>
</div>
</template>


<script>
import studentData from "../data/student";

export default {
  name: "Demo",
  data() {
    return {
      searchedScore: 0,
      filterRegular: "up",
      menuShow: false,
      menuList: [],
      checkedList: [],
    };
  },
  methods: {
    // 设置值
    setScore(e) {
      // 拿到输入框的值
      this.searchedScore = Number(e.target.value);
      // 输入时 -> 调用过滤数据的函数 -> 筛选出展示在menu中的数据
      this.filterStudents();
    },
    // 设置filterRegular
    setFilterRegular(type) {
      this.filterRegular = type;
      // 切换时 -> 调用过滤数据的函数 -> 筛选出展示在menu中的数据
      this.filterStudents();
    },
    // 根据输入框的值this.searchedScore + 以上/以下this.filterRegular 过滤数据
    filterStudents() {
      switch (this.filterRegular) {
        case "up":
          // filter返回一个新数组
          this.menuList = studentData.filter(
            (item) => item.score >= this.searchedScore
          );
          break;
        case "down":
          this.menuList = studentData.filter(
            (item) => item.score < this.searchedScore
          );
          break;
        default:
          break;
      }

      // 如果有数据 就要展示meun
      this.menuShow = this.menuList.length > 0; // 用这一句话实现menuShow是true 还是 false的判断
    },
    setChecked(info) {
      // 是否存在这个数据
      const hasThisItem = getChecked(info.id);

      // 点击checkbox每次只能操作一个数据
      // -> 如果存在 再次点击 -> 要删除这个数据 -> 也就是拿除了当前id的其他数据
      // -> 如果不存在 点击 -> 把这个数据push到checkedList中去
      if (hasThisItem) {
        this.checkedList = this.checkedList.filter((item) => {
          item.id !== info.id;
        });
      } else {
        this.checkedList.push(info);
      }
    },
    getChecked(id) {
      // 返回的布尔值
      return this.checkedList.some((item) => item.id === id);
    },
  },
};
</script>


<style lang="scss" acoped>
.active {
  background-color: #000;
  color: #fff;
}
</style>

1.3. 🔴代码 没有组件化之前的代码

<template>
<div class="search-component">
    <div class="input-box">
        <input 
        type="number" 
        placeholder="请输入分数"
        @input="setScore"
        :value="searchedScore"
        />
    </div>
    <!-- 筛选xx分 以上/以下 的人 -->
    <!-- 被选中的时候加上类active实现特殊样式 -->
    <button 
    @click="setFilterRegular('up')"
    :className="['btn', { active: filterRegular === 'up'}]"
    >
        以上
    </button>
    <button 
    @click="setFilterRegular('down')"
    :className="['btn', { active: filterRegular === 'down'}]"
    >
        以下
    </button>
    <div>
    <div class="mune" v-show="menuShow">
        <ul>
            <li 
            v-for="item of menuList"
            :key="item.id"
            >
            <input type="checkbox" 
            @click="setChecked(item)"
            :checked="getChecked(item.id)"
            />
            <span>{{ item.name }}</span>
            <span>{{ item.score }}</span>
            </li>
        </ul>
    </div>
    <div class="checked-list">
         <ul>
            <li 
            v-for="item of checkedList"
            :key="item.id"
            >
            <input type="checkbox" 
            @click="setChecked(item)"
            />
            <span>{{ item.name }}</span>
            <span>{{ item.score }}</span>
            </li>
        </ul>
    </div>
</div>
</template>


<script>
import studentData from "../data/student";

export default {
  name: "Demo",
  data() {
    return {
      searchedScore: 0,
      filterRegular: "up",
      menuShow: false,
      menuList: [],
      checkedList: [],
    };
  },
  methods: {
    // 设置值
    setScore(e) {
      // 拿到输入框的值
      this.searchedScore = Number(e.target.value);
      // 输入时 -> 调用过滤数据的函数 -> 筛选出展示在menu中的数据
      this.filterStudents();
    },
    // 设置filterRegular
    setFilterRegular(type) {
      this.filterRegular = type;
      // 切换时 -> 调用过滤数据的函数 -> 筛选出展示在menu中的数据
      this.filterStudents();
    },
    // 根据输入框的值this.searchedScore + 以上/以下this.filterRegular 过滤数据
    filterStudents() {
      switch (this.filterRegular) {
        case "up":
          // filter返回一个新数组
          this.menuList = studentData.filter(
            (item) => item.score >= this.searchedScore
          );
          break;
        case "down":
          this.menuList = studentData.filter(
            (item) => item.score < this.searchedScore
          );
          break;
        default:
          break;
      }

      // 如果有数据 就要展示meun
      this.menuShow = this.menuList.length > 0; // 用这一句话实现menuShow是true 还是 false的判断
    },
    setChecked(info) {
      // 是否存在这个数据
      const hasThisItem = getChecked(info.id);

      // 点击checkbox每次只能操作一个数据
      // -> 如果存在 再次点击 -> 要删除这个数据 -> 也就是拿除了当前id的其他数据
      // -> 如果不存在 点击 -> 把这个数据push到checkedList中去
      if (hasThisItem) {
        this.checkedList = this.checkedList.filter((item) => {
          item.id !== info.id;
        });
      } else {
        this.checkedList.push(info);
      }
    },
    getChecked(id) {
      // 返回的布尔值
      return this.checkedList.some((item) => item.id === id);
    },
  },
};
</script>


<style lang="scss" acoped>
.active {
  background-color: #000;
  color: #fff;
}
</style>

2. 组件化

组件: 维护性 扩展性

先不要考虑复用性

给search传递什么? 数据 -> 响应式 -> 传递

<template>
  <search :data="students"></search>
</template>


<script>
// import Demo from "./components/Demo";

import Search from "./components/Search.vue";
import students from "./data/student";

export default {
  name: "App",
  components: {
    Search,
  },
  data() {
    return {
      // 数据变成响应式的
      students,
    };
  },
};
</script>

2.1. 🌟封装input组件

如何让组件有更高的复用性 --> 都集合到父组件,让父组件去操作

数据: input的type交给使用者定义, placeholder/value 也是属性传递进来的

方法: 给input事件绑定的方法setValue也是为了发送事件的和参数的

<template>
  <input
    :type="type"
    :placeholder="placeholder"
    :value="value"
    @input="setValue"
  />
</template>


<script>
export default {
  name: "MyInput",
  props: {
    value: String | Number,
    type: {
      // type的值不能随便写 有固定的值的 要验证
      validator(value) {
        return ["text", "password", "number", "email"].includes(value);
      },
    },
    placeholder: String,
  },
  methods: {
    setValue(e) {
      const _value = e.target.value;

      // 发送事件 
      if (_value.length > 0) {
        this.$emit("input", _value);
      }
    },
  },
};
</script>

父组件使用input组件

<template>
  <div class="input-component">
    <my-input
      type="number"
      placeholder="请输入您要查询的成绩"
      :value="searchedScore"
      @input="setScore"
    ></my-input>
  </div>
</template>


<script>
import MyInput from "./MyInput.vue";

export default {
  name: "Search",
  components: {
    MyInput,
  },
  data() {
    return {
      searchedScore: 0,
    };
  },
  methods: {
    // input事件是input组件发送的事件 还发送了输入框的值value -> 在这里就可以拿到输入框的值来赋值给响应式数据
    setScore(value) {
      this.searchedScore = Number(value);
    },
  },
};
</script>

2.2. 🌟封装button-group组件

遍历数据才生成对应的MyButton组件

active是动态变化的,不是写死的,要根据情况来判断

点击button要更改选中的button状态,但是current属性又是传递进来的。只能发送自定义事件给父组件来修改

<template>
  <button :class="['my-btn', type, { active }]">
    <slot></slot>
  </button>
</template>


<script>
export default {
  name: "MyButton",
  props: {
    type: {
      default: primary,
      validator(value) {
        return ["primary", "success", "danger", "warning"].includes(value);
      },
    },
    active: {
      type: Boolean,
      default: false,
    },
  },
};
</script>


<style lang="scss">
.my-btn {
  &.primary {
    background-color: blue;
    color: #fff;
  }

  &.success {
    background-color: green;
    color: #fff;
  }

  &.danger {
    background-color: red;
    color: #fff;
  }

  &.warning {
    background-color: orange;
    color: #fff;
  }

  &.active {
    background-color: #fff;
    color: #333;
  }
}
</style>
<template>
  <div class="btn-group">
    <my-button
      :type="type"
      :active="current === item.regular"
      v-for="item of regulars"
      :key="item.regular"
      @click="setCurrent(item.regular)"
      >{{ item.text }}</my-button
    >
  </div>
</template>


<script>
import MyButton from "./MyButton.vue";

export default {
  name: "MyBtnGroup",
  components: {
    MyButton,
  },
  props: {
    type: {
      type: String,
      default: primary,
    },
    current: String,
    regulars: Array,
    /**
     *
     * regulars: [
     *  {
     *   regular: 'up',
     *   text: '以上'
     *  },
     *  {
     *   regular: 'down',
     *   text: '以下'
     *  },
     * ]
     */
  },
  methods: {
    setCurrent(regular) {
      // regular是up / down
      this.$emit("click", regular);
    },
  },
};
</script>


<style lang="scss">
.btn-group {
  display: inline-block;
}
</style>
<template>
  <div class="input-component">
    <my-input
      type="number"
      placeholder="请输入您要查询的成绩"
      :value="searchedScore"
      @input="setScore"
    ></my-input>
    <my-btn-group
      :type="type"
      :current="currentRegular"
      :regulars="regulars"
      @click="setCurrentRegular"
    ></my-btn-group>
  </div>
</template>


<script>
import MyInput from "./MyInput.vue";
import MyBtnGroup from "./MyInput.vue";

export default {
  name: "Search",
  components: {
    MyInput,
    MyBtnGroup,
  },
  data() {
    return {
      searchedScore: 0,
      type: "primary",
      currentRegular: "up",
      regulars: [
        {
          regular: "up",
          text: "以上",
        },
        {
          regular: "down",
          text: "以下",
        },
      ],
    };
  },
  methods: {
    // input事件是input组件发送的事件 还发送了输入框的值value -> 在这里就可以拿到输入框的值来赋值给响应式数据
    setScore(value) {
      this.searchedScore = Number(value);
    },

    // 设置是up or down
    setCurrentRegular(regular) {
      this.currentRegular = regular;
    },
  },
};
</script>

2.3. 🌟自定义checkbox组件

<template>
  <div class="my-check-box">
    <label :for="id" class="my-check-box-lbl">
      <input type="checkbox" :id="id" @click="doCheck" />
      <span class="icon"></span>
    </label>
    <div class="text">
      <slot></slot>
    </div>
  </div>
</template>


<script>
export default {
  name: "MyCheckBox",
  props: {
    id: Number | String,
  },
  methods: {
    doCheck() {
        // 必须要抛出去
        this.$emit('click', this.id);
    },
  },
};
</script>


<style lang="scss">
.my-check-box {
  display: inline-block;

  .my-check-box-lbl {
    display: inline-block;
    position: relative;
    width: 12px;
    height: 12px;
    border: 1px solid #333;
    border-radius: 50%;
    margin-right: 5px;

    input {
      visibility: hidden;

      &:checked + .icon {
        display: block;
      }
    }

    .icon {
      display: none;
      position: absolute;
      top: 3px;
      left: 3px;
      width: 6px;
      height: 6px;
      background-color: blue;
      border-radius: 50%;
    }
  }
}
</style>

2.4. 🌟MyMenu组件

<template>
  <div class="my-menu" v-show="show">
    <my-check-box v-for="item of data" :key="item.id" :id="item.id">
      <span>{{ item.name }}</span>
      <span>{{ item.score }}</span>
    </my-check-box>
  </div>
</template>


<script>
import MyCheckBox from "./MyCheckBox.vue";

export default {
  name: "MyMenu",
  components: {
    MyCheckBox,
  },
  props: {
    data: Array,
    show: {
      type: Boolean,
      default: false,
    },
  },
};
</script>


<style lang="scss">
.my-menu {
  width: 300px;
  border: 1px solid #000;
}
</style>

3. 🌟复用UI组件及完成菜单与列表联动功能

3.1. 完成menuList展示 数据联动

<template>
  <div class="input-component">
    <my-input
      type="number"
      placeholder="请输入您要查询的成绩"
      :value="searchedScore"
      @input="setScore"
    ></my-input>
    <my-btn-group
      :type="type"
      :current="currentRegular"
      :regulars="regulars"
      @click="setCurrentRegular"
    ></my-btn-group>
    <my-menu :show="menuShow" :data="menuList"> </my-menu>
  </div>
</template>


<script>
import MyInput from "./MyInput.vue";
import MyBtnGroup from "./MyInput.vue";

export default {
  name: "Search",
  components: {
    MyInput,
    MyBtnGroup,
  },
  props: {
    data: Array,
  },
  data() {
    return {
      searchedScore: 0,
      type: "primary",
      currentRegular: "up",
      menuShow: false,
      regulars: [
        {
          regular: "up",
          text: "以上",
        },
        {
          regular: "down",
          text: "以下",
        },
      ],
      menuList: [],
    };
  },
  methods: {
    // input事件是input组件发送的事件 还发送了输入框的值value -> 在这里就可以拿到输入框的值来赋值给响应式数据
    setScore(value) {
      // input组件发送事件时会传递value(也就是e.target.value)
      this.searchedScore = Number(value);
      this.filterStudents();
    },

    // btn-group发送的事件 设置是up or down
    setCurrentRegular(regular) {
      // 修改up or down btn-group发送的事件会传递regular(up or down )
      this.currentRegular = regular;
      this.filterStudents();
    },
    // 过滤学生
    filterStudents() {
      switch (this.currentRegular) {
        case "up":
          this.menuList = this.data.filter(
            (item) => item.score > this.searchedScore
          );
          break;
        case "down":
          this.menuList = this.data.filter(
            (item) => item.score <= this.searchedScore
          );
          break;
        default:
          break;
      }

      // 根据menu有没有数据 来决定要不要展示menu
      this.menuShow = this.menuList.length > 0;
    },
  },
};
</script>

3.2. 完成checkedList展示 数据联动

子组件: 接受数据 渲染页面

定义点击事件: 用来发送自定义事件

<template>
  <div class="my-menu" v-show="show">
    <my-check-box
      v-for="item of data"
      :key="item.id"
      :id="item.id"
      @click="setChecked(item.id)"
    >
      <span>{{ item.name }}</span>
      <span>{{ item.score }}</span>
    </my-check-box>
  </div>
</template>


<script>
import MyCheckBox from "./MyCheckBox.vue";

export default {
  name: "MyMenu",
  components: {
    MyCheckBox,
  },
  props: {
    data: Array,
    checkedData: Array,
    show: {
      type: Boolean,
      default: false,
    },
  },
  methods: {
    setChecked(id) {
      this.$emit(
        "click",
        this.data.find((item) => item.id === id)
      );
    },
  },
};
</script>


<style lang="scss">
.my-menu {
  width: 300px;
  border: 1px solid #000;
}
</style>

父组件: 给子组件传递已经处理好的数据

接受子组件发送的方法,处理数据

<template>
  <div class="input-component">
    <my-input
      type="number"
      placeholder="请输入您要查询的成绩"
      :value="searchedScore"
      @input="setScore"
    ></my-input>
    <my-btn-group
      :type="type"
      :current="currentRegular"
      :regulars="regulars"
      @click="setCurrentRegular"
    ></my-btn-group>
    <my-menu
      :show="menuShow"
      :data="menuList"
      :checkedData="checkedList"
      @click="setChecked"
    >
    </my-menu>
  </div>
</template>


<script>
import MyInput from "./MyInput.vue";
import MyBtnGroup from "./MyInput.vue";

export default {
  name: "Search",
  components: {
    MyInput,
    MyBtnGroup,
  },
  props: {
    data: Array,
  },
  data() {
    return {
      searchedScore: 0,
      type: "primary",
      currentRegular: "up",
      menuShow: false,
      regulars: [
        {
          regular: "up",
          text: "以上",
        },
        {
          regular: "down",
          text: "以下",
        },
      ],
      menuList: [],
      checkedList: [],
    };
  },
  methods: {
    // input事件是input组件发送的事件 还发送了输入框的值value -> 在这里就可以拿到输入框的值来赋值给响应式数据
    setScore(value) {
      // input组件发送事件时会传递value(也就是e.target.value)
      this.searchedScore = Number(value);
      this.filterStudents();
    },

    // btn-group发送的事件 设置是up or down
    setCurrentRegular(regular) {
      // 修改up or down btn-group发送的事件会传递regular(up or down )
      this.currentRegular = regular;
      this.filterStudents();
    },
    // 过滤学生
    filterStudents() {
      switch (this.currentRegular) {
        case "up":
          this.menuList = this.data.filter(
            (item) => item.score > this.searchedScore
          );
          break;
        case "down":
          this.menuList = this.data.filter(
            (item) => item.score <= this.searchedScore
          );
          break;
        default:
          break;
      }
      // 根据menu有没有数据 来决定要不要展示menu
      this.menuShow = this.menuList.length > 0;
    },
    // 是否已经存在这个数据
    getChecked(id) {
      return this.checkedList.some((item) => item === id);
    },
    // 设置checkedList
    setChecked(info) {
      //是否已经存在这个数据
      const hasThisItem = getChecked(info);

      if (hasThisItem) {
        this.checkedList = this.checkedList.filter(
          (item) => item.id !== info.id
        );
      } else {
        this.checkedList.push(info);
      }

      this.$emit('setCheckedList', this.checkedList);
    },
  },
};
</script>


<style>
</style>

3.3. App.vue

<template>
  <div id="app">
    <search ref="search" :data="students" @setCheckedList="setCheckedList"></search>
    <div class="my-table">
      <table border="1" v-if="checkedList.length > 0">
        <thead>
          <tr>
            <th>操作</th>
            <th>姓名</th>
            <th>分数</th>
          </tr>
        </thead>
        <tbody>
          <tr v-for="item of checkedList" :key="item.id">
            <td>
              <my-check-box
                :id="item.id"
                checked="true"
                @click="setChecked(item)"
              >
              </my-check-box>
            </td>
            <td>{{ item.name }}</td>
            <td>{{ item.score }}</td>
          </tr>
        </tbody>
      </table>
    </div>
  </div>
</template>


<script>
// import Demo from "./components/Demo";

import Search from "./components/Search.vue";
import students from "./data/student";

export default {
  name: "App",
  components: {
    Search,
  },
  data() {
    return {
      // 数据变成响应式的
      students,
      checkedList: []
    };
  },
  methods: {
    setCheckedList(checkedStudents) {
      this.checkedList = checkedStudents;
    },
    setChecked(info){
      this.$refs.search.setChecked(info);
    }
  },
};
</script>

4. 🌟菜单隐藏的自定义指令

点击菜单的其他区域 要隐藏menuList checkedList区域

<template>
  <div class="input-component" v-menu-hide="setMenuHide">
    <my-input
      type="number"
      placeholder="请输入您要查询的成绩"
      :value="searchedScore"
      @input="setScore"
    ></my-input>
    <my-btn-group
      :type="type"
      :current="currentRegular"
      :regulars="regulars"
      @click="setCurrentRegular"
    ></my-btn-group>
    <my-menu
      :show="menuShow"
      :data="menuList"
      :checkedData="checkedList"
      @click="setChecked"
    >
    </my-menu>
  </div>
</template>


<script>
import MyInput from "./MyInput.vue";
import MyBtnGroup from "./MyInput.vue";

import menuHide from "../directives/menuHide";

export default {
  name: "Search",
  components: {
    MyInput,
    MyBtnGroup,
  },
  directives: {
    menuHide,
  },
  props: {
    data: Array,
  },
  data() {
    return {
      searchedScore: 0,
      type: "primary",
      currentRegular: "up",
      menuShow: false,
      regulars: [
        {
          regular: "up",
          text: "以上",
        },
        {
          regular: "down",
          text: "以下",
        },
      ],
      menuList: [],
      checkedList: [],
    };
  },
  methods: {
    // input事件是input组件发送的事件 还发送了输入框的值value -> 在这里就可以拿到输入框的值来赋值给响应式数据
    setScore(value) {
      // input组件发送事件时会传递value(也就是e.target.value)
      this.searchedScore = Number(value);
      this.filterStudents();
    },

    // btn-group发送的事件 设置是up or down
    setCurrentRegular(regular) {
      // 修改up or down btn-group发送的事件会传递regular(up or down )
      this.currentRegular = regular;
      this.filterStudents();
    },
    // 过滤学生
    filterStudents() {
      switch (this.currentRegular) {
        case "up":
          this.menuList = this.data.filter(
            (item) => item.score > this.searchedScore
          );
          break;
        case "down":
          this.menuList = this.data.filter(
            (item) => item.score <= this.searchedScore
          );
          break;
        default:
          break;
      }
      // 根据menu有没有数据 来决定要不要展示menu
      this.menuShow = this.menuList.length > 0;
    },
    // 是否已经存在这个数据
    getChecked(id) {
      return this.checkedList.some((item) => item === id);
    },
    // 设置checkedList
    setChecked(info) {
      //是否已经存在这个数据
      const hasThisItem = getChecked(info);

      if (hasThisItem) {
        this.checkedList = this.checkedList.filter(
          (item) => item.id !== info.id
        );
      } else {
        this.checkedList.push(info);
      }
    },
    setMenuHide() {
      this.menuShow = false;
    },
  },
};
</script>


<style>
</style>
// 自定义指令默认导出一个对象
export default {
    // 绑定时干吗 el绑定的元素 bindings上有方法setMenuHide vnode中可以拿到组件的实例
    bind(el, bindings, vnode) {
        el.handler = function (e) {
            // 点击的元素是否包含在el中
            if (!el.contains(e.target)) {
                // 不包含 隐藏

                // 方法
                const _method = bindings.expression;

                // 拿到组件实例去调用方法
                vnode.context[_method]();
            }
        }
        document.addEventListener('click', el.handler, false);
    },
    // 解绑时干吗
    unbind(el, bindings, vnode) {
        document.removeListener('click', el.handler, false);
    }
}

5. 代码 组件化

5.1. App.vue

<template>
  <div id="app">
    <search
      ref="search"
      :data="students"
      @setCheckedList="setCheckedList"
    ></search>
    <div class="my-table">
      <table border="1" v-if="checkedList.length > 0">
        <thead>
          <tr>
            <th>操作</th>
            <th>姓名</th>
            <th>分数</th>
          </tr>
        </thead>
        <tbody>
          <tr v-for="item of checkedList" :key="item.id">
            <td>
              <my-check-box
                :id="item.id"
                :checked="true"
                @click="setChecked(item)"
              >
              </my-check-box>
            </td>
            <td>{{ item.name }}</td>
            <td>{{ item.score }}</td>
          </tr>
        </tbody>
      </table>
    </div>
  </div>
</template>


<script>
// import Demo from "./components/Demo";

import Search from "./components/Search.vue";
import MyCheckBox from "./components/MyCheckBox.vue";

import students from "./data/student";

export default {
  name: "App",
  components: {
    Search,
  },
  data() {
    return {
      // 数据变成响应式的
      students,
      checkedList: [],
    };
  },
  methods: {
    setCheckedList(checkedStudents) {
      this.checkedList = checkedStudents;
    },
    setChecked(info) {
      this.$refs.search.setChecked(info);
    },
  },
};
</script>


<style>
</style>

5.2. components

5.2.1. Search.vue

<template>
  <div class="input-component" v-menu-hide="setMenuHide">
    <my-input
      type="number"
      placeholder="请输入您要查询的成绩"
      :value="searchedScore"
      @input="setScore"
    ></my-input>
    <my-btn-group
      :type="type"
      :current="currentRegular"
      :regulars="regulars"
      @click="setCurrentRegular"
    ></my-btn-group>
    <my-menu
      :show="menuShow"
      :data="menuList"
      :checkedData="checkedList"
      @click="setChecked"
    >
    </my-menu>
  </div>
</template>


<script>
import MyInput from "./MyInput.vue";
import MyBtnGroup from "./MyInput.vue";

import menuHide from "../directives/menuHide";

export default {
  name: "Search",
  components: {
    MyInput,
    MyBtnGroup,
  },
  directives: {
    menuHide,
  },
  props: {
    data: Array,
  },
  data() {
    return {
      searchedScore: 0,
      type: "primary",
      currentRegular: "up",
      menuShow: false,
      regulars: [
        {
          regular: "up",
          text: "以上",
        },
        {
          regular: "down",
          text: "以下",
        },
      ],
      menuList: [],
      checkedList: [],
    };
  },
  methods: {
    // input事件是input组件发送的事件 还发送了输入框的值value -> 在这里就可以拿到输入框的值来赋值给响应式数据
    setScore(value) {
      // input组件发送事件时会传递value(也就是e.target.value)
      this.searchedScore = Number(value);
      this.filterStudents();
    },

    // btn-group发送的事件 设置是up or down
    setCurrentRegular(regular) {
      // 修改up or down btn-group发送的事件会传递regular(up or down )
      this.currentRegular = regular;
      this.filterStudents();
    },
    // 过滤学生
    filterStudents() {
      switch (this.currentRegular) {
        case "up":
          this.menuList = this.data.filter(
            (item) => item.score > this.searchedScore
          );
          break;
        case "down":
          this.menuList = this.data.filter(
            (item) => item.score <= this.searchedScore
          );
          break;
        default:
          break;
      }
      // 根据menu有没有数据 来决定要不要展示menu
      this.menuShow = this.menuList.length > 0;
    },
    // 是否已经存在这个数据
    getChecked(id) {
      return this.checkedList.some((item) => item === id);
    },
    // 设置checkedList
    setChecked(info) {
      //是否已经存在这个数据
      const hasThisItem = getChecked(info);

      if (hasThisItem) {
        this.checkedList = this.checkedList.filter(
          (item) => item.id !== info.id
        );
      } else {
        this.checkedList.push(info);
      }
    },
    setMenuHide() {
      this.menuShow = false;
    },
  },
};
</script>


<style>
</style>

5.2.2. MyInput.vue

<template>
  <input
    :type="type"
    :placeholder="placeholder"
    :value="value"
    @input="setValue"
  />
</template>


<script>
export default {
  name: "MyInput",
  props: {
    value: String | Number,
    type: {
      // type的值不能随便写 有固定的值的 要验证
      validator(value) {
        return ["text", "password", "number", "email"].includes(value);
      },
    },
    placeholder: String,
  },
  methods: {
    setValue(e) {
      const _value = e.target.value;

      // 发送事件
      if (_value.length > 0) {
        this.$emit("input", _value);
      }
    },
  },
};
</script>


<style>
</style>

5.2.3. MyButton.vue

<template>
  <button :class="['my-btn', type, { active }]">
    <slot></slot>
  </button>
</template>


<script>
export default {
  name: "MyButton",
  props: {
    type: {
      default: primary,
      validator(value) {
        return ["primary", "success", "danger", "warning"].includes(value);
      },
    },
    active: {
      type: Boolean,
      default: false,
    },
  },
};
</script>


<style lang="scss">
.my-btn {
  &.primary {
    background-color: blue;
    color: #fff;
  }

  &.success {
    background-color: green;
    color: #fff;
  }

  &.danger {
    background-color: red;
    color: #fff;
  }

  &.warning {
    background-color: orange;
    color: #fff;
  }

  &.active {
    background-color: #fff;
    color: #333;
  }
}
</style>

5.2.4. MyBtnGroup.vue

<template>
  <div class="btn-group">
    <my-button
      :type="type"
      :active="current === item.regular"
      v-for="item of regulars"
      :key="item.regular"
      @click="setCurrent(item.regular)"
      >{{ item.text }}</my-button
    >
  </div>
</template>


<script>
import MyButton from "./MyButton.vue";

export default {
  name: "MyBtnGroup",
  components: {
    MyButton,
  },
  props: {
    type: {
      type: String,
      default: primary,
    },
    current: String,
    regulars: Array,
    /**
     *
     * regulars: [
     *  {
     *   regular: 'up',
     *   text: '以上'
     *  },
     *  {
     *   regular: 'down',
     *   text: '以下'
     *  },
     * ]
     */
  },
  methods: {
    setCurrent(regular) {
      // regular是up / down
      this.$emit("click", regular);
    },
  },
};
</script>


<style lang="scss">
.btn-group {
  display: inline-block;
}
</style>

5.2.5. MyCheckBox.vue

<template>
  <div class="my-check-box">
    <label :for="id" class="my-check-box-lbl">
      <input type="checkbox" :id="id" @click="doCheck" v-bind="$attrs" />
      <span class="icon"></span>
    </label>
    <div class="text">
      <slot></slot>
    </div>
  </div>
</template>


<script>
export default {
  name: "MyCheckBox",
  inheritAttrs: false,
  props: {
    id: Number | String,
  },
  methods: {
    doCheck() {
      // 必须要抛出去
      this.$emit("click", this.id);
    },
  },
};
</script>


<style lang="scss">
.my-check-box-lbl {
  display: inline-block;
  position: relative;
  width: 12px;
  height: 12px;
  border: 1px solid #333;
  border-radius: 50%;
  margin-right: 5px;
  vertical-align: -5px;

  input {
    visibility: hidden;

    &:checked + .icon {
      display: block;
    }
  }

  .icon {
    display: none;
    position: absolute;
    top: 3px;
    left: 3px;
    width: 6px;
    height: 6px;
    background-color: blue;
    border-radius: 50%;
  }
}
</style>

5.2.6. MyMenu.vue

<template>
  <div class="my-menu" v-show="show">
    <my-check-box
      v-for="item of data"
      :key="item.id"
      :id="item.id"
      :checked="computedChecked(item.id)"
      @click="setChecked(item.id)"
    >
      <span>{{ item.name }}</span>
      <span>{{ item.score }}</span>
    </my-check-box>
  </div>
</template>


<script>
import MyCheckBox from "./MyCheckBox.vue";

export default {
  name: "MyMenu",
  components: {
    MyCheckBox,
  },
  props: {
    data: Array,
    checkedData: Array,
    show: {
      type: Boolean,
      default: false,
    },
  },
  methods: {
    setChecked(id) {
      this.$emit(
        "click",
        this.data.find((item) => item.id === id)
      );
    },
    computedChecked(id) {
      return this.checkedData.some((item) => item.id === id);
    },
  },
};
</script>


<style lang="scss">
.my-menu {
  width: 300px;
  border: 1px solid #000;
}
</style>

5.3. directives

5.3.1. menuHide.js

// 自定义指令默认导出一个对象
export default {
    // 绑定时干吗 el绑定的元素 bindings上有方法setMenuHide vnode中可以拿到组件的实例
    bind(el, bindings, vnode) {
        el.handler = function (e) {
            // 点击的元素是否包含在el中
            if (!el.contains(e.target)) {
                // 不包含 隐藏

                // 方法
                const _method = bindings.expression;

                // 拿到组件实例去调用方法
                vnode.context[_method]();
            }
        }
        document.addEventListener('click', el.handler, false);
    },
    // 解绑时干吗
    unbind(el, bindings, vnode) {
        document.removeListener('click', el.handler, false);
    }
}
  • 21
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以为你提供一个简单的示例代码,用于展示如何使用Vue组件Vue路由、Vuex和Axios来实现增删改查功能。 首先,我们需要安装VueVue Router、Vuex和Axios,你可以使用npm或yarn来安装它们。 接下来,我们创建一个Vue组件,用于显示数据列表和执行增删改查操作。在这个组件中,我们将使用Vuex来管理数据状态和执行异步操作,使用Axios来进行HTTP请求。 ```html <template> <div> <h1>Todo List</h1> <form @submit.prevent="addTodo"> <input v-model="newTodo" placeholder="Add new todo" /> <button type="submit">Add</button> </form> <ul> <li v-for="(todo, index) in todos" :key="todo.id"> <input type="checkbox" :checked="todo.completed" @change="toggleTodo(todo)" /> <span>{{ todo.text }}</span> <button @click="removeTodo(index)">Remove</button> </li> </ul> </div> </template> <script> import { mapGetters, mapActions } from 'vuex'; export default { computed: { ...mapGetters(['todos']), }, methods: { ...mapActions(['addTodo', 'removeTodo', 'toggleTodo']), }, data() { return { newTodo: '', }; }, }; </script> ``` 接下来,我们使用Vue Router来创建路由和路由组件,用于在不同的URL路径下显示不同的组件。在这个例子中,我们将创建一个路由,用于显示TodoList组件。 ```javascript import Vue from 'vue'; import VueRouter from 'vue-router'; import TodoList from './components/TodoList.vue'; Vue.use(VueRouter); const routes = [ { path: '/', component: TodoList, }, ]; const router = new VueRouter({ mode: 'history', routes, }); export default router; ``` 最后,我们使用Vuex来管理TodoList组件的状态和操作。我们将创建一个store对象,包含state、mutations、actions和getters。 ```javascript import Vue from 'vue'; import Vuex from 'vuex'; import axios from 'axios'; Vue.use(Vuex); const store = new Vuex.Store({ state: { todos: [], }, mutations: { SET_TODOS(state, todos) { state.todos = todos; }, ADD_TODO(state, todo) { state.todos.push(todo); }, REMOVE_TODO(state, index) { state.todos.splice(index, 1); }, TOGGLE_TODO(state, todo) { todo.completed = !todo.completed; }, }, actions: { async fetchTodos({ commit }) { const response = await axios.get('/api/todos'); commit('SET_TODOS', response.data); }, async addTodo({ commit }, text) { const response = await axios.post('/api/todos', { text, completed: false }); commit('ADD_TODO', response.data); }, async removeTodo({ commit }, index) { await axios.delete(`/api/todos/${state.todos[index].id}`); commit('REMOVE_TODO', index); }, async toggleTodo({ commit }, todo) { await axios.patch(`/api/todos/${todo.id}`, { completed: !todo.completed }); commit('TOGGLE_TODO', todo); }, }, getters: { todos: state => state.todos, }, }); export default store; ``` 现在我们已经完成了使用Vue组件Vue路由、Vuex和Axios实现增删改查功能的示例代码。当然,这只是一个简单的示例,你可以根据自己的需求进行修改和扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值