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

Vue.js可复用搜索筛选器与网格布局实现问题求助

哈哈,这个需求我太懂了!之前做后台管理系统的时候也碰到过,不想写一堆重复的筛选和布局代码,咱们一步步来搞定这俩问题,保证代码干净又高效~

核心思路:封装独立的可复用组件

每个section本质上都是“筛选输入框 + 三列网格展示”的组合,而且各自的数据源独立、筛选状态独立——那直接把这个组合封装成一个独立组件就完事儿了!每个组件实例对应一个section,天然互相隔离,完全不用担心影响其他模块。

1. 组件化实现:让每个section自给自足

先写一个叫FilterableGridSection的子组件,把所有通用逻辑都塞进去,父组件只需要传对应的标题和数据源就行。

子组件代码(FilterableGridSection.vue)

<template>
  <section class="grid-section">
    <!-- 每个section自己的标题 -->
    <h2>{{ sectionTitle }}</h2>
    <!-- 独立的筛选输入框(如果要全局统一筛选,就把这个移到父组件,用props传关键词) -->
    <input 
      v-model="localFilter" 
      placeholder="搜索{{ sectionTitle }}内容..."
      class="filter-input"
    />
    <!-- 三列网格布局 -->
    <div class="three-column-grid">
      <div v-for="(column, idx) in splitFilteredContents" :key="idx" class="grid-column">
        <div v-for="item in column" :key="item.id" class="grid-card">
          <h3>{{ item.title }}</h3>
          <!-- 这里放你的内容,比如描述、图片之类的 -->
          <p>{{ item.desc }}</p>
        </div>
      </div>
    </div>
  </section>
</template>

<script>
export default {
  name: 'FilterableGridSection',
  props: {
    // 每个section的标题
    sectionTitle: {
      type: String,
      required: true
    },
    // 每个section对应的数据源
    contents: {
      type: Array,
      required: true,
      // 简单校验:每个item必须有title字段用于筛选
      validator: val => val.every(item => item.title)
    },
    // 可选:如果需要全局统一筛选,就用这个props替代localFilter
    // globalFilter: {
    //   type: String,
    //   default: ''
    // }
  },
  data() {
    return {
      // 每个section自己的筛选关键词,互不干扰
      localFilter: ''
    }
  },
  computed: {
    // 第一步:通用筛选逻辑,用computed缓存结果,只有依赖变化才重新计算
    filteredContents() {
      const keyword = this.localFilter.toLowerCase();
      // 如果用全局筛选,就换成this.globalFilter
      return this.contents.filter(item => 
        item.title.toLowerCase().includes(keyword)
      );
    },
    // 第二步:把筛选后的内容分成三列,同样用computed缓存
    splitFilteredContents() {
      const columns = [[], [], []];
      this.filteredContents.forEach((item, idx) => {
        // 按索引取模分配到三列
        columns[idx % 3].push(item);
      });
      return columns;
    }
  }
}
</script>

<style scoped>
.grid-section {
  margin: 2rem 0;
}
.filter-input {
  margin-bottom: 1.5rem;
  padding: 0.6rem;
  border-radius: 4px;
  border: 1px solid #ddd;
  width: 100%;
  max-width: 320px;
}
.three-column-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.2rem;
}
.grid-card {
  padding: 1rem;
  border: 1px solid #eee;
  border-radius: 6px;
  background: #fff;
  box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
</style>

父组件调用方式

在父组件里,你只需要循环或者逐个渲染这个子组件,给每个组件传不同的sectionTitle和contents就行:

<template>
  <div class="app-wrapper">
    <!-- 如果是全局统一筛选,就在这里加输入框,绑定globalFilter -->
    <!-- <input v-model="globalFilter" placeholder="全局搜索所有内容..." /> -->

    <FilterableGridSection 
      sectionTitle="Section 1"
      :contents="contents1"
      <!-- :global-filter="globalFilter" -->
    />
    <FilterableGridSection 
      sectionTitle="Section 2"
      :contents="contents2"
    />
    <FilterableGridSection 
      sectionTitle="Section 3"
      :contents="contents3"
    />
  </div>
</template>

<script>
import FilterableGridSection from './FilterableGridSection.vue';

export default {
  components: {
    FilterableGridSection
  },
  data() {
    return {
      globalFilter: '',
      // 你的三个数据源
      contents1: [{ id: 1, title: '标题1', desc: '内容1' }, ...],
      contents2: [{ id: 1, title: '标题A', desc: '内容A' }, ...],
      contents3: [{ id: 1, title: '标题X', desc: '内容X' }, ...]
    }
  }
}
</script>

2. 用computed解决性能和复用问题

你担心的“如何区分各section内容”其实根本不是问题——因为每个组件实例都是独立的,它们的computed属性只会监听自己的contents和localFilter(或globalFilter),完全不会和其他组件的状态混淆。而且computed会自动缓存计算结果,只有当依赖的响应式数据变化时才会重新计算,比每次渲染都执行的method性能好太多。

进阶优化:让组件更灵活

如果以后需要调整筛选字段(比如不是按title,而是按category筛选),或者修改列数,只需要给子组件加几个props就行:

// 在子组件的props里新增
filterField: {
  type: String,
  default: 'title' // 默认按title筛选
},
columnCount: {
  type: Number,
  default: 3, // 默认三列
  validator: val => val > 0
}

然后修改computed里的逻辑:

filteredContents() {
  const keyword = this.localFilter.toLowerCase();
  return this.contents.filter(item => 
    item[this.filterField].toLowerCase().includes(keyword)
  );
},
splitFilteredContents() {
  // 按配置的列数生成空数组
  const columns = Array.from({ length: this.columnCount }, () => []);
  this.filteredContents.forEach((item, idx) => {
    columns[idx % this.columnCount].push(item);
  });
  return columns;
}

这样组件的复用性就更强了,想怎么改就怎么改~

内容的提问来源于stack exchange,提问作者Deep

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 06:40:14