Vue前端复杂数据过滤器的可扩展存储与管理方案咨询
前端复杂筛选设置与状态管理方案
1. 结构化筛选状态,拆分逻辑单元
将单一扁平的filters对象按筛选类型分组,让结构更清晰,也便于扩展不同资源(电影/剧集/书籍)的专属筛选条件:
// 按类型分组的响应式筛选状态 const filters = reactive({ // 基础分类筛选 category: { genre: "", complete: false, mediaType: "" // 新增区分资源类型 }, // 时间范围筛选 dateRange: { releaseYearAfter: null, releaseYearBefore: null, watchDateAfter: null // 扩展观看时间筛选 }, // 排序配置 sort: { orderBy: "userRecordCreatedAt", orderDirection: "DESC NULLS LAST" }, // 文本搜索 search: { title: "" } });
2. 封装筛选逻辑到独立状态管理模块
把筛选的设置、重置、参数转换逻辑抽离到Pinia/Vuex Store中,避免组件内逻辑冗余混乱:
示例:Pinia Store实现筛选管理
// stores/filterStore.js import { defineStore } from 'pinia'; import { reactive, computed } from 'vue'; export const useFilterStore = defineStore('filter', () => { const filters = reactive({ category: { genre: "", complete: false }, dateRange: { releaseYearAfter: null, releaseYearBefore: null }, sort: { orderBy: "userRecordCreatedAt", orderDirection: "DESC NULLS LAST" }, search: { title: "" } }); // 自动计算激活的筛选条件(无需手动维护) const activeFilters = computed(() => { const active = []; Object.entries(filters).forEach(([group, conditions]) => { Object.entries(conditions).forEach(([key, value]) => { const defaultValue = getDefaultValue(group, key); if (value !== defaultValue) { active.push({ group, key, value }); } }); }); return active; }); // 设置单个筛选条件 function setFilter(group, key, value) { filters[group][key] = value; applyFilters(); } // 重置指定组或所有筛选条件 function resetFilter(group = null) { if (group) { Object.assign(filters[group], getDefaultGroupValues(group)); } else { Object.assign(filters, getDefaultAllFilters()); } applyFilters(); } // 转换为后端需要的API参数格式(自动移除空值/默认值) function toApiParams() { const params = {}; Object.entries(filters).forEach(([group, conditions]) => { Object.assign(params, conditions); }); return Object.fromEntries( Object.entries(params).filter(([_, value]) => value !== null && value !== "" && value !== false) ); } // 调用API并更新数据 async function applyFilters() { const params = toApiParams(); const response = await LibraryApiService.applyFilters(params); // 假设将结果存入store的items中 // items.value = response.data; } return { filters, activeFilters, setFilter, resetFilter, applyFilters }; }); // 辅助函数:获取各筛选项的默认值 function getDefaultValue(group, key) { const defaults = { category: { genre: "", complete: false }, dateRange: { releaseYearAfter: null, releaseYearBefore: null }, sort: { orderBy: "userRecordCreatedAt", orderDirection: "DESC NULLS LAST" }, search: { title: "" } }; return defaults[group][key]; } function getDefaultGroupValues(group) { return getDefaultValue(group); } function getDefaultAllFilters() { return { category: getDefaultValue('category'), dateRange: getDefaultValue('dateRange'), sort: getDefaultValue('sort'), search: getDefaultValue('search') }; }
3. UI组件化,统一筛选交互
将不同类型的筛选控件封装成独立组件,组件内部直接调用Store方法,减少重复代码:
示例:题材筛选组件
<template> <div class="filter-group"> <h3>题材筛选</h3> <MenuItem v-for="genre in genres" :key="genre.name"> <a class="filter-item-link" :class="{ active: filters.category.genre === genre.name }" @click="setGenre(genre.name)" > {{ genre.name }} </a> </MenuItem> <button @click="resetGenre">重置</button> </div> </template> <script setup> import { useFilterStore } from '@/stores/filterStore'; import { storeToRefs } from 'pinia'; const filterStore = useFilterStore(); const { filters } = storeToRefs(filterStore); const genres = [...]; // 从接口或全局状态获取题材列表 function setGenre(genreName) { filterStore.setFilter('category', 'genre', genreName); } function resetGenre() { filterStore.setFilter('category', 'genre', ''); } </script> <style scoped> .filter-item-link.active { color: #2c3e50; font-weight: bold; border-bottom: 2px solid #2c3e50; } </style>
示例:年份范围筛选组件(处理"2000年后发行"这类复杂条件)
<template> <div class="filter-group"> <h3>发行年份</h3> <input type="number" placeholder="起始年份" v-model="yearAfter" @change="updateRange"> <span>-</span> <input type="number" placeholder="结束年份" v-model="yearBefore" @change="updateRange"> <button @click="resetRange">重置</button> </div> </template> <script setup> import { useFilterStore } from '@/stores/filterStore'; import { storeToRefs } from 'pinia'; import { ref } from 'vue'; const filterStore = useFilterStore(); const { filters } = storeToRefs(filterStore); const yearAfter = ref(filters.value.dateRange.releaseYearAfter); const yearBefore = ref(filters.value.dateRange.releaseYearBefore); function updateRange() { filterStore.setFilter('dateRange', 'releaseYearAfter', yearAfter.value || null); filterStore.setFilter('dateRange', 'releaseYearBefore', yearBefore.value || null); } function resetRange() { yearAfter.value = null; yearBefore.value = null; updateRange(); } </script>
4. 自动跟踪并展示激活的筛选条件
利用Store中的activeFilters计算属性,自动收集所有非默认值的筛选条件,可直接渲染成标签供用户查看和移除:
<template> <div class="active-filters"> <span class="filter-tag" v-for="filter in activeFilters" :key="`${filter.group}-${filter.key}`"> {{ getDisplayText(filter) }} <button @click="removeFilter(filter)">×</button> </span> </div> </template> <script setup> import { useFilterStore } from '@/stores/filterStore'; import { storeToRefs } from 'pinia'; const filterStore = useFilterStore(); const { activeFilters } = storeToRefs(filterStore); function getDisplayText(filter) { switch(filter.group) { case 'category': if (filter.key === 'genre') return `题材: ${filter.value}`; if (filter.key === 'complete') return filter.value ? '已完成' : '未完成'; case 'dateRange': if (filter.key === 'releaseYearAfter') return `发行年份 ≥ ${filter.value}`; if (filter.key === 'releaseYearBefore') return `发行年份 ≤ ${filter.value}`; case 'search': if (filter.key === 'title') return `标题包含: ${filter.value}`; default: return `${filter.key}: ${filter.value}`; } } function removeFilter(filter) { const defaultValue = getDefaultValue(filter.group, filter.key); filterStore.setFilter(filter.group, filter.key, defaultValue); } </script>
5. 可选:与URL参数同步(支持刷新保留状态)
如果需要页面刷新后保留筛选状态,可将筛选参数同步到URL的query中:
// 在filterStore中添加URL同步逻辑 import { useRoute, useRouter } from 'vue-router'; import { onMounted } from 'vue'; const route = useRoute(); const router = useRouter(); // 从URL初始化筛选状态 function initFromUrl() { const query = route.query; if (query.genre) filters.category.genre = query.genre; if (query.releaseYearAfter) filters.dateRange.releaseYearAfter = parseInt(query.releaseYearAfter); if (query.releaseYearBefore) filters.dateRange.releaseYearBefore = parseInt(query.releaseYearBefore); // 其他参数映射... } // 应用筛选时更新URL async function applyFilters() { const params = toApiParams(); const response = await LibraryApiService.applyFilters(params); // items.value = response.data; router.push({ query: params }); } // 页面加载时初始化 onMounted(() => { initFromUrl(); applyFilters(); });
内容的提问来源于stack exchange,提问作者Daniel Richter
相关产品推荐
相关产品推荐

