记录Vue 3.0/多条件联动筛选业务设计

概述


当业务代码中,出现多种筛选并且筛选之间需要互相关联筛选的时候,正确的做法是:

将所有的筛选条件都封装成一个对象,包含N个筛选条件,默认值则是筛选框的默认值

所有的筛选框都应绑定同一个事件,将他们的值赋值进入“筛选对象”中

然后封装一个公共的“筛选方法”,将“筛选对象”中所有的筛选条件逐个筛选。

最终返回符合所有条件的数据

不论“筛选对象”中的哪一个值变化时,都将触发整个“筛选对象”的筛选。

这样设计的话,可以大大降低联合筛选的代码复杂性,即便后期有新筛选条件加入,也只需要修改“筛选对象”与“筛选方法”,无非就是加一个条件判断

封装筛选条件对象

将所有的筛选条件封装成一个统一的对象(例如 filterObject),每个筛选条件作为该对象的一个属性。
这些属性的默认值应与对应筛选框的初始默认值保持一致。

统一绑定筛选事件

所有的筛选框组件应绑定同一个事件处理器,用于监听筛选条件的变化。在事件触发时,更新 filterObject 中对应筛选条件的值,以保证筛选状态的统一性。

封装公共筛选方法

设计并封装一个通用的筛选方法,例如 applyFilters,用于处理 filterObject 中的所有筛选条件:

  • 遍历 filterObject 的每个属性,并将其作为筛选规则。
  • 根据这些条件,逐步过滤原始数据集合,最终返回符合所有条件的筛选结果。

统一触发筛选

无论 filterObject 中的哪一个属性值发生变化,都应触发统一的筛选逻辑。这种机制确保每次筛选的结果始终是最新且精准的,且无需额外关注具体条件的来源。

可扩展性设计

当需要新增筛选条件时,只需在 filterObject 中添加新属性,并在 applyFilters 方法中增加相关的筛选逻辑,无需改动核心结构或现有筛选条件的代码。

示例代码(仅为示例)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
<template>
<div>
<!-- 搜索框 -->
<el-row :gutter="20" class="filter-row">
<el-col :span="8">
<el-input
v-model="filterObject.search"
placeholder="请输入关键字"
clearable
>
<template #prefix>
<el-icon><Search /></el-icon>
</template>
</el-input>
</el-col>

<!-- 类型筛选 -->
<el-col :span="8">
<el-select
v-model="filterObject.type"
placeholder="请选择类型"
clearable
>
<el-option label="全部" value="all" />
<el-option label="2D" value="2D" />
<el-option label="3D" value="3D" />
</el-select>
</el-col>

<!-- 标签筛选 -->
<el-col :span="8">
<el-checkbox-group v-model="filterObject.detectionType">
<el-checkbox label="工程" />
<el-checkbox label="设计" />
<el-checkbox label="建筑" />
<el-checkbox label="工业" />
</el-checkbox-group>
</el-col>
</el-row>

<!-- 筛选结果 -->
<el-row>
<el-col :span="24">
<el-card>
<h3>筛选结果:</h3>
<el-table :data="applyFilters" style="width: 100%">
<el-table-column prop="name" label="名称" />
<el-table-column prop="type" label="类型" />
<el-table-column
label="标签"
v-slot="scope"
>
{{ scope.row.tags.join(', ') }}
</el-table-column>
</el-table>
</el-card>
</el-col>
</el-row>
</div>
</template>

<script lang="ts">
import { reactive, computed, defineComponent } from "vue";
import { ElMessage } from "element-plus";
import { Search } from "@element-plus/icons-vue";

export default defineComponent({
components: {
Search,
},
setup() {
// 示例数据
const dataList = [
{ id: 1, name: "CAD图纸1", type: "2D", tags: ["工程", "设计"] },
{ id: 2, name: "CAD图纸2", type: "3D", tags: ["建筑", "工程"] },
{ id: 3, name: "CAD图纸3", type: "2D", tags: ["工业", "设计"] },
];

// 筛选条件对象
const filterObject = reactive({
search: "", // 搜索关键字
type: "all", // 类型筛选,默认值为 "all" 表示不过滤
detectionType: [] as string[], // 多选标签筛选,默认为空数组
});

// 筛选结果,基于筛选条件动态计算
const applyFilters = computed(() => {
const result = dataList.filter((item) => {
// 条件 1: 类型筛选
const matchType =
filterObject.type === "all" || item.type === filterObject.type;

// 条件 2: 模糊搜索
const matchSearch = filterObject.search
? item.name.includes(filterObject.search)
: true;

// 条件 3: 多选标签筛选
const matchDetection = filterObject.detectionType.length
? filterObject.detectionType.every((tag) => item.tags.includes(tag))
: true;

// 返回符合所有条件的数据
return matchType && matchSearch && matchDetection;
});

// 提示用户无匹配结果
if (result.length === 0) {
ElMessage.info("没有符合条件的数据!");
}

return result;
});

return {
filterObject,
applyFilters,
};
},
});
</script>