筛选升级:探索通用筛选表单组件的封装新技巧

  • 作者简介:大家好,我是文艺理科生Owen,某车企前端开发,负责AIGC+RAG项目
  • 目前在卷的技术方向:工程化系列,主要偏向最佳实践
  • 希望可以在评论区交流互动,感谢支持~~~

最近公司接到了一个简易BI报表的需求,其中有多个页面是表单筛选+表格数据显示的需求。

在如今AIGC盛行的时代,这种CRUD的常规需求都可以用低代码平台搭建或者用AI直接生成了。类似于官方示例一样:

<template>
  <div>
    <el-form :inline="true" :model="formInline" class="demo-form-inline">
      <el-form-item label="审批人">
        <el-input v-model="formInline.user" placeholder="审批人"></el-input>
      </el-form-item>
      <el-form-item label="活动区域">
        <el-select v-model="formInline.region" placeholder="活动区域">
          <el-option label="区域一" value="shanghai"></el-option>
          <el-option label="区域二" value="beijing"></el-option>
        </el-select>
      </el-form-item>
     <el-form-item>
       <el-button type="primary" @click="onSubmit">查询</el-button>
     </el-form-item>
   </el-form>
  </div>
</template>

优点是容易维护,缺点是扩展性差。综合考虑还是封装一个通用的表单筛选组件,这样会加快后续的业务迭代速度。

最初是这样设计的,如下图,每个页面只需导入对应的配置数据,就可以直接展示对应页面。通用组件目前只有下拉筛选类文本输入类两种(基于element ui),单独定义两种类型即可。使用时只需维护好配置数据,通用组件可以得到最大程度的复用。

其中通用筛选表单组件设计如下:

就这样,按照上面的方案封装完成后,开心地使用组件(摸鱼)。示例项目采用vue-cli创建,vue2语法,默认已安装和注册了element ui组件库。如果仍对安装注册过程不清晰的同学,可跳转至element ui快速上手。具体代码如下:

<template>
  <div class="container">
    <el-form :inline="true" :model="formData" class="demo-form-inline">
      <template v-for="(item, index) in config">
        <el-form-item v-if="item.type === 'select'" :label="item.label">
          <el-select v-model="formData[item.name]" :placeholder="`请选择${item.label}`">
            <el-option v-for="option in options[item.name]" :label="option.label" :value="option.value"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item v-else-if="item.type === 'input'" :label="item.label">
          <el-input v-model="formData[item.name]" :placeholder="`请输入${item.label}`"></el-input>
        </el-form-item>
      </template>
      <el-form-item>
        <el-button @click="onReset">重置</el-button>
        <el-button type="primary" @click="onSubmit">搜索</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>

<script>
export default {
  name: 'SearchForm',
  props: {
    config: {
      type: Array,
      default: () => []
    },
    options: {
      type: Object,
      default: () => []
    }
  },
  data() {
    return {
      formData: {},
    }
  },
  methods: {
    onSubmit() {
      this.$emit('submit', this.formData)
    },
    onReset() {
      this.formData = {}
    }
  }
}
</script>

<style scoped>
.container {
  width: 100%;
}
</style>
<!-- App.vue -->
<template>
  <div id="app">
    <SearchForm :config="config" :options="options" @submit="getSearchResult" />
  </div>
</template>

<script>
import SearchForm from './components/SearchForm.vue'

export default {
  name: 'App',
  components: {
    SearchForm
  },
  async mounted() {
    this.options = await this.getOptions()
  },
  data() {
    return {
      config: [
        {
          type: 'input',
          label: '用户名称',
          name: 'username',
        },
        {
          type: 'select',
          label: '用户状态',
          name: 'status'
        }
      ],
      options: {}
    }
  },
  methods: {
    getSearchResult(res) {
      console.log(res, '筛选的条件为:')
    },
    getOptions() {
      // 筛选项由一个异步请求整体返回,模拟后端接口
      return Promise.resolve({
        status: [
          {
            label: '正常',
            value: 0
          },
          {
            label: '停用',
            value: 1
          }
        ]
      })
    }
  }
}
</script>

原计划筛选项列表为一个接口整体返回,整体传入即可。但不幸的是,接到了需求变更,要求每一个筛选项都需要来源于不同的异步请求(正常options都是直接传入的),而且用户每点击一次,更新一次。顿时深感社会险恶,但不甘心一腔热血付之东流。

我们来梳理一下目前的难点:

  • 每个options都需要一个异步请求,应该如何与动态配置config进行关联?
  • 用户每点击一次就进行异步请求刷新一次筛选列表

传入不同异步请求下的筛选数据

目前config的成员对象为:

{
  type: 'select',
  label: '用户状态',
  name: 'status'
}

经过调研,有一个初步的想法:尝试在成员对象中增加一个字段,对应值为一个promise对象,将其传入子组件,当el-select的下拉事件触发时,调用promise对象,将返回的筛选项数据存储在一个对象中。

修改后config的成员对象为:

{
  type: 'select',
  label: '用户状态',
  name: 'status',
  loadOption: () => this.getStatusOption
}

其中getStatusOption函数如下:

export default {
  // ...其他代码
  methods: {
    getStatusOption() {
      // 筛选项由一个异步请求整体返回,模拟后端接口
      return Promise.resolve([
        {
          label: '正常',
          value: 0
        },
        {
          label: '停用',
          value: 1
        }
      ])
    }
  }
}

然后子组件中统一调用每个loadOption函数

<script>
export default {
  name: 'SearchForm',
  data() {
    return {
      formData: {},
      options: {},
    }
  },
  methods: {
    getOptions() {
      this.config.forEach(async item => {
        // 仅有loadOption属性的才会调用
        if(item.loadOption) {
          const option = await item.loadOption()()
          this.$set(this.options, item.name, option)
        }
      })
    }
  }
}
</script>

这样实现了不同字段对应不同的异步请求。

另外,当异步请求需要传参时,传入的loadOptions需要用bind传入(因为仅做传入,不进行调用)

{
  type: 'select',
  label: '用户状态',
  name: 'status',
  loadOption: () => this.getStatusOption.bind(null, 'arg1', 'arg2')
}

每点击一次刷新一次筛选列表

接下来解决每点击一次刷新的问题。

这里用到visible-change事件,仅当下拉框出现时触发(需要屏蔽隐藏时触发的情况)

子组件的getOptions函数修改如下:

<template>
  <!-- 省略其他代码 -->
  <el-select v-model="formData[item.name]" :placeholder="`请选择${item.label}`" @visible-change="getOptions">
    <el-option v-for="option in options[item.name]" :label="option.label" :value="option.value"></el-option>
  </el-select>
  <!-- 省略其他代码 -->
</template>

<script>
export default {
  name: 'SearchForm',
  data() {
    return {
      formData: {},
      options: {},
    }
  },
  methods: {
    getOptions(visible) {
      // 下拉列表隐藏时不需要刷新筛选项
      if(!visible) return;
      this.config.forEach(async item => {
        if(item.loadOption) {
          const option = await item.loadOption()()
          this.$set(this.options, item.name, option)
        }
      })
    }
  }
}
</script>

经历了九九八十一难后,终于封装完成。又可以开心地摸鱼了。

总结:本文通过封装一个通用表单筛选组件,解决了由不同的异步请求获取筛选项、点击触发刷新的问题,增强了组件的复用性。

demo源码:github

日拱一卒,功不唐捐。

  • 17
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值