You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 08:25:16