zoukankan      html  css  js  c++  java
  • 基于 element-plus 封装一个依赖 json 动态渲染的查询控件

    前情回顾

    基于 el-form 封装一个依赖 json 动态渲染的表单控件
    Vue3 封装第三方组件(一)做一个合格的传声筒

    功能

    使用 vue3 + element-plus 封装了一个查询控件,专为管理后台量身打造,支持各种查询需求:

    • 多种查询方式
    • 快捷查询
    • 更多查询
    • 自定义查询
    • 支持防抖
    • 清空每个查询条件
    • 依赖 json 动态创建

    有些控件自带清空功能,有些没有自带清空功能,那么就需求我们手动加上清空的功能。

    技术栈

    • Vite2
    • Vue3
    • element-plus

    查询控件设计

    【自我感觉良好的一个脑图】
    查询控件设计.png

    在线演示

    https://naturefw.gitee.io/nf-vue-cdn/elecontrol/

    进入页面后请点击“查询控件”。
    可以在“表单控件”里面添加测试数据,数据会存入webSQL。
    受限于webSQL,有些查询功能无法实现。

    功能演示

    查询功能具体是什么样子呢?我们先来看一段视频:
    点击查看视频演示

    各种查询方式

    查询控件针对不同的数据类型(后端数据库字段类型),量身打造了多种查询方式,让查询更便捷!

    文本

    文本类的查询

    针对文本类的数据类型(varchar、text等),提供常用的模糊查询(包含)、精确查询(=),还有起始于、结束于等查询方式可供选择。
    这样用户可以更灵活方便的进行查询操作。

    数字

    数字查询

    针对数值类型(int、float、decme等),提供常用的精确查询(=)、范围查询(从xx到xxx)还有大于等于等查询方式。

    单选组的查询

    单选组

    单选组的多选

    针对枚举类型,int 或者 varchar 等有限数量。

    单选组有两种情况,一个是常见的查询一种情况即可,选择第一选项那么只需要显示第一个选项对应的数据。
    另一个就是想同时看多个选项的结果,那么这时候还用单选组的方式就不适合了,需要变成多选组的方式,这样才可以让用户选择多个选项。

    所以这里的单选的查询支持两种查询方式:

    • =: 只能查询一个选项,对应单选。
    • 包含:可以同时查询多个选项,对应多选。

    支持清空查询条件,即点击右侧的“x”。
    多选支持防抖。

    勾选和开关

    勾选和开关

    二者对应的数据类型是 bool 型的(bit),所以只有“=”这一种查询方式,增加了一个“清空”的按钮,这样可以单独清掉查询条件。

    级联选择

    联动下拉

    常见的级联选择是省市区县的选择,组件默认给的model是一个数组形式,有多少级就会有多少个数组。

    但是在后端数据库里面,往往会分成多个字段来存放,比如省份用一个字段表示,城市用一个字段表示,区县又是一个字段表示。

    那么我们在查询的时候,就需要把查询结果按照字段给拆分开,这样才便于查询。

    所以这里把查询结果按照字段拆分开然后在返回给后端,比如这样:
    { "a": [ 401, "zhinan" ], "b": [ 401, "shejiyuanze" ], "c": [ 401, "yizhi" ] }

    日期

    日期查询比较复杂,这里对应的数据类型是date,选择后返回的数据是“2021-05-20”的形式。
    然后就是如何让用户感觉爽的问题了。

    • 常规查询方式

    日期查询

    一般都是如上图所示,直接选择日期范围,这个看起来似乎没有啥问题,可以选择任意日期。

    但是如果用户想查询2021年1月到2021年3月的数据,那么用户的操作就会比较繁琐。
    我们来看看一共要点击几次鼠标?
    打开日期栏 》 找到一月份(n次) 》 选择一号 》 找到三月份(又是n次) 》选择31号。
    整个流程需要点好多次鼠标,实在是太麻烦了。

    • 通过月份查询日期范围
      如果可以直接选择月份呢?像这样:

    通过月份选择日期

    如果用户想选择多个月份的日期,可以通过“从” + “年月”的形式,选择起始月份即可,返回的数据是"2021-01-01", "2021-03-31" 的形式。

    选择一个月的范围

    如果客户想选择一个月的范围,那么可以用“=” + “年月”的方式来选择(如上图),返回的数据是"2021-02-01", "2021-02-28" 的形式。

    这样用户就非常方便了,节省了n次鼠标点击。不过这还没有结束,还有选择“年”的情况。

    • 通过年查询日期范围
      如果要查询一年的或者多年的日期范围呢?我们可以选择“年”的方式。

    选择一整年的方式

    如果选择一整年的话,我们可以使用“=” + “年”的方式(如上图),选择需要的年份即可,返回的数据是 "2021-01-01", "2021-12-31" 的形式。

    通过年来选择日期范围

    如果选择连续的多个年份,可以用“从” + “年”的方式(如上图),选择起始年份即可,返回的数据是"2021-01-01", "2022-12-31" 的形式。

    年、年月、年周的查询

    上面是针对date类型的数据,这里是针对int、varchar类型的数据。
    有时候为了加快查询速度,数据库设计上面可能会用增加“冗余字段”的方式来提升性能,比如增加“年”的字段,类型是int,存放“2021”、“2022”这样的数据。
    同理,可以增加“年月”的字段,类型是int,存放“202101”、“202103”这类的数据,还有“年周”的情况。

    这里的查询方式就是针对这种情况来设计的。

    • 年的查询

    年

    年的范围

    要比日期查询简单很多。

    • 年月的查询

    月份

    月份范围

    • 年周的查询

    这里不是指星期几,而是一年内的第几周,听说有些企业是按照周来安排工作的,所以这里也提供了周的查询。

    周

    多个周

    日期时间的查询

    日期时间查询

    快速查询

    显示常用的查询条件。

    快捷查询

    自定义查询方案

    可以把常用的查询字段放在一起,组成一个查询方案,方便用户使用。

    自定义查询方案

    更多查询

    显示全部查询条件,查询后的字段可以带入快捷查询,便于随时更改查询条件。

    显示全部查询字段

    查询条件带入快捷查询

    文件结构

    上面都是介绍功能,下面开始介绍一下实现方式。
    首先看一下文件结构:

    查询控件的文件结构

    • packages
      存放基础的js,和UI库无关的基本逻辑代码,很显然等稳定后会发布到npm上面,以便于支持其他UI库。
      目前有表单子控件、表单控件、查询子控件、查询控件,以后还会有列表控件、按钮控件等。

    • control-web
      web 控件的意思。存放组件的UI部分。至于会不会发布到npm,目前还没有想好,因为有个灵活性的问题。

    • views
      这里就是如何使用的代码了。

    实现方式

    我们以文本类的查询为例进行介绍,我们先做一个查询方式的组件,然后做一个文本的查询子控件。

    查询方式

    <template>
      <el-dropdown @command="handleCommand">
        <span class="el-dropdown-link">
          {{kindName}}<i
            class=" el-icon--right"
            :class="{'el-icon-arrow-down': isUp, 'el-icon-arrow-up': !isUp}"></i>
        </span>
        <template #dropdown>
          <el-dropdown-menu>
            <el-dropdown-item
              v-for="kindId in findKind"
              :key="'s_kind_'+ kindId"
              :command="kindId"
              >
                {{findKindList[kindId].name}}
              </el-dropdown-item>
          </el-dropdown-menu>
        </template>
      </el-dropdown>
    </template>
    

    使用 el-dropdown 做一个选择列表。

    import { defineComponent, ref } from 'vue'
    import { findKindList } from '/nf-control-web'
    
    // 查询方式的 select
    export default defineComponent({
      name: 'el-find-kind',
      props: {
        // 返回选择的查询方式
        modelValue: [Number],
        // 需要显示的查询方式
        findKind: {
          type: Array,
          default: () => { return [411] }
        }
      },
      emits: ['update:modelValue', 'change'],
      setup (props, context) {
        const kindName = ref(findKindList[props.modelValue].name)
    
        const handleCommand = (command) => {
          kindName.value = findKindList[command].name
          context.emit('update:modelValue', command)
          context.emit('change', command)
        }
    
        return {
          isUp,
          kindName,
          findKindList,
          handleCommand
        }
      }
    })
    

    设置属性,接收查询方式,和用户选择的查询方式。

    查询子控件

    <template>
      <!--查询方式-->
      <div style="float:left;65px;text-align:center;">
        <find-kind
          v-model="findChoiceKind"
          :findKind="findKind"
          @change="myChange"
        />
      </div>
      <!--查询内容-->
      <div :style="{ (150 * colCount - 10 ) + 'px'}" style="float:left;">
        <div style="float:left;" :style="{ (150 * colCount - 40 ) + 'px'}">
          <component
            :is="ctlList[controlType]"
            v-model="findText"
            v-bind="$attrs"
            :delay="delay"
            :colName="colName"
            @myChange="myChange">
          </component>
        </div>
      </div>
    </template>
    

    放置查询方式和查询用的组件。

    import { defineComponent } from 'vue'
    // 引入查询子控件的管理类
    import { findItemManage } from '/nf-control-web'
    // 查询方式的控件
    import selectFindKind from './s-findkind.vue'
    
    // 异步组件,引入表单子控件
    import { formItemToFindItem } from '../nf-el-find-item/map-el-find-item.js'
    
    /*
    * 查询子控件,文本类
    * * 单行文本
    * * 多行文本
    * * ulr、电话、邮箱等
    */
    export default defineComponent({
      name: 'el-find-item-text',
      inheritAttrs: false,
      props: {
        controlId: Number, // 控件ID
        controlType: Number, // 控件类型
        colName: String, // 字段名称
        modelValue: [Array, String], // 查询结果,数组形式
        colCount: { // 占用空间
          type: Number,
          default: 1
        },
        findKind: { // 查询方式
          type: Array, // , 407, 408
          default: () => { return [403, 401, 402, 404, 405, 406] }
        },
        delay: { // 防抖
          type: Number,
          default: 600
        }
      },
      components: {
        'find-kind': selectFindKind
      },
      emits: ['update:modelValue', 'my-change'],
      setup (props, context) {
        // 表单子控件 to 查询子控件 的 字典
        const ctlList = formItemToFindItem
      
        const {
          findChoiceKind, // 选择的查询方式
          findText, // 一个关键字查询
          mySubmit
        } = findItemManage(props, context)
    
        // 设置默认查询方式
        findChoiceKind.value = props.findKind[0]
    
        // 提交查询结果
        const myChange = () => {
          // 一个关键字查询
          mySubmit(findText.value)
        }
    
        return {
          ctlList, // 控件字典,用于加载具体的控件
          findChoiceKind, // 查询方式
          findText, // 一个查询关键字
          myChange // 触发提交事件
        }
      }
    })
    

    设置需要的属性,比如具体的查询方式、防抖时间间隔等。因为文本查询比较简单,所以只需要简单的提交查询条件即可。

    查询控件

    <template>
      <!--快捷查询-->
      <el-card class="box-card">
        <el-scrollbar>
          <div class="flex-content" style="min-400px;">
            <el-form
              inline
              label-position="right"
              :model="findItemModel"
              ref="formControl"
              class="demo-form-expand"
              label-width="1px"
              size="mini"
            >
              <el-form-item style="100px">
                <el-dropdown size="small">
                  <el-button type="primary">
                    快捷查询<i class="el-icon-arrow-down el-icon--right"></i>
                  </el-button>
                  <template #dropdown>
                    <el-dropdown-menu>
                      <el-dropdown-item
                        @click="quickClick(0)"
                      >
                        快捷查询
                      </el-dropdown-item>
                      <el-dropdown-item
                        v-for="(item, key, index) in customer"
                        :key="'quick_' + index"
                        @click="quickClick(key)"
                      >
                        {{item.title}}
                      </el-dropdown-item>
                    </el-dropdown-menu>
                  </template>
                </el-dropdown>
              </el-form-item>
              <el-form-item
                v-for="(ctrId, index) in arrQuickFind"
                :key="'find_quick_'+index"
                style="border:1px solid #cfe1f3;min-height:33px;"
                :style="{ ( 160 * getCtrMeta(ctrId).colCount + 80) + 'px'}"
              >
                <!--判断要不要加载插槽-->
                <template v-if="getCtrMeta(ctrId).controlType === 1">
                  <slot :name="ctrId">父组件没有设置插槽</slot>
                </template>
                <!--查询的子控件,采用动态组件的方式-->
                <template v-else>
                  <component
                    :is="ctlList[getCtrMeta(ctrId).controlType]"
                    v-model="findItemModel[ctrId]"
                    v-bind="getCtrMeta(ctrId)"
                    @myChange="mySubmit">
                  </component>
                </template>
              </el-form-item>
              <el-form-item style="60px">
                <el-button type="primary" round @click="moreOpen">更多</el-button>
              </el-form-item>
            </el-form>
          </div>
        </el-scrollbar>
      </el-card>
      <!--更多查询,放在抽屉里面-->
      <findmore
        :allFind="allFind"
        :reload="reload"
        :itemMeta="itemMeta"
        :findKind="findKind"
        :moreFind="moreFind"
        v-model:isShow="isShow"
      />
    </template>
    

    这里是快捷查询,更多查询做成了单独的组件,这样可以让模板代码简洁一点,不至于太乱。

    /**
     * @function div 格式的查询控件
     * @description 可以依据 json 动态生成查询控件
     * @returns {*} Vue 组件,查询控件
     */
    export default {
      name: 'el-find-div',
      components: {
        findmore
      },
      props: {
        ...findProps
      },
      setup (props, context) {
        // 控件字典
        const ctlList = findItemListKey
    
        // 依据ID获取组件的meta,因为 model 不支持[]嵌套
        const getCtrMeta = (id) => {
          return props.itemMeta[id] || {}
        }
      
        const {
          moreFind, // 接收更多查询 更多查询里面子控件的事件
          isShow, // 抽屉是否打开
          arrQuickFind, // 快捷栏的数组
          findItemModel, // 查询子控件的model
          moreOpen, // 点击更多,清空快捷
          quickClick, // 个性化方案的单击事件
          mySubmit // 查询子控件的事件
        } = findManage(props, context)
    
        return {
          isShow, // 抽屉是否打开
          moreFind, // 接收更多查询
          arrQuickFind, // 快捷栏的数组
          ctlList, // 子控件字典
          resetForm, // 重置表单
          formControl, // 获取表单的dom
          getCtrMeta, // 返回子控件的meta
          findItemModel, // 查询子控件的model
          moreOpen, // 点击更多,清空快捷
          quickClick, // 个性化方案的单击事件
          mySubmit
        }
      }
    }
    

    代码比较多,这里是 setup 部分,主要负责代码函数的整合。减少代码混乱的程度。

    使用方式

    <template>
      <!--演示查询控件-->
      <nf-find
        v-model="query"
        v-bind="formProps"
      />
      查询条件:{{query}}
      <!--数据列表 演示查询结果-->
      <findGrid :dataList="dataList"/>
      <!--可以分页-->
      <findPager/>
      
    </template>
    

    模板部分比较简单了,设置好属性即可。

    import { reactive, watch } from 'vue'
    // 组件
    import findCom from '../control-web/nf-el-find/el-find-div.vue'
    import findGrid from './find-grid.vue'
    import findPager from './find-pager.vue'
    // 加载json文件
    import json from '/json/find-test.json'
    
    // 数据列表的状态
    import dataListControl from '../control/data-list'
    
    export default {
      name: 'eleFindComponent',
      components: {
        findGrid,
        findPager,
        'nf-find': findCom
      },
      setup () {
        const query = reactive({})
      
        const findTest = json.findTest
        // 设置查询控件的属性
        const findProps = reactive({})
        // 添加重新绑定的开关
        findProps.reload = false
    
        // 模拟异步加载meta
        Object.assign(findProps, findTest.baseProps)
        findProps.itemMeta = findTest.itemMeta // 表单子控件的属性
        findProps.findKind = findTest.findKind // 查询方式
        
        return {
          query,
          dataList,
          // 渲染表单的meta
          findProps 
        }
      }
    }
    

    这里主要是加载json文件,然后给查询控件设置属性。

    然后获得查询条件,提交给后端API申请数据即可。

    json 文件的格式

    比较长,发个图片示意一下:

    查询控件需要的 json 数据

    更多代码欢迎查看源码。

    源码

    https://gitee.com/naturefw/nf-vite2-element

    在线演示

    https://naturefw.gitee.io/nf-vue-cdn/elecontrol/

  • 相关阅读:
    Java WebService入门实例
    Maven是什么
    for 循环
    2.4 DevOps工程师的故事:构建运行时的严谨性
    2.3.3 构建微服务的入口:Spring Boot控制器
    2.3.2 引导Spring Boot应用程序:编写引导类
    2.1.3 互相交流:定义服务接口
    第2章 使用Spring Boot构建微服务
    第1章 欢迎来到Cloud和Spring
    第一次在博客园开博
  • 原文地址:https://www.cnblogs.com/jyk/p/14841929.html
Copyright © 2011-2022 走看看