基於 element-plus 封裝一個依賴 json 動態渲染的查詢控制元件

金色海洋(jyk)發表於2021-06-02

前情回顧

基於 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;width:65px;text-align:center;">
    <find-kind
      v-model="findChoiceKind"
      :findKind="findKind"
      @change="myChange"
    />
  </div>
  <!--查詢內容-->
  <div :style="{width: (150 * colCount - 10 ) + 'px'}" style="float:left;">
    <div style="float:left;" :style="{width: (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-width:400px;">
        <el-form
          inline
          label-position="right"
          :model="findItemModel"
          ref="formControl"
          class="demo-form-expand"
          label-width="1px"
          size="mini"
        >
          <el-form-item style="width: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="{width: ( 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="width: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/

相關文章