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