文檔驅動 —— 查詢組件:使用 vue3.0 的新特性,重構代碼


源碼

https://github.com/naturefwvue/nf-vue3-ant

目的

  • 簡單方便,不用寫代碼就可以實現各種查詢功能
  • 把查詢相關的功能都做全,不留遺憾

功能

  • 快捷查詢,常用的查詢條件
  • 個性化查詢方案,自己設置自己喜歡的查詢條件
  • 可以更換各種查詢方式,模糊、精確、范圍,隨你喜歡
  • 更多的查詢條件,查詢條件不夠用?那就都加上
  • meta驅動,無需代碼,不寫代碼,夢想成真

結構

快捷查詢


如上圖,把常用的幾個查詢條件放在第一行,采用緊湊模式,直接放控件,通過 placeholder 的方式標示控件是哪個字段的,這樣在有限的空間里面可以多放一兩個字段。
下面可以放功能按鈕(添加等)和數據列表,想要查詢直接點就行,不用點個按鈕,在打開個某某,麻煩。

個性化查詢方案

每個人都可以有自己的查詢習慣,我喜歡(或者工作需要)用這幾個查詢條件,你喜歡那幾個查詢條件,快捷查詢里就那么幾個位置,到底放哪幾個字段?
不用掙,我們可以按照自己的需要設置不同的查詢方案,放在快捷里面,你喜歡就行,不影響別人

更換各種查詢方式

想要用訂單編號查詢,使用模糊查詢還是精確查詢?
精確查詢需要把訂單號都輸入進去,麻煩。
模糊查詢,有可能出現不需要的數據。

以前做項目,遇到訂單號規則升級。老編號五位,新編號十位。用戶想查老訂單,把訂單號都輸入全了,結構還是查到一堆不想要的訂單,因為是模糊查詢。

現在好了,用戶可以自己選擇是模糊查詢還是精確查詢。

數字類型可以選擇等於還是區間查詢,甚至大於、小於這些查詢方式都可以加上。

日期類的查詢,也可以選擇是范圍查詢,還是查詢某一天。

這樣我們做設計的時候就不用糾結,這個字段到底怎么查才適合,把可能的查詢方式都給客戶,客戶自己選好了。

更多的查詢條件

有些模塊,里面的字段非常多,再怎么個性化設置也不夠用,那么就需要把全部可以查詢的字段都拿出來顯示,於是就有了這個全部查詢

多行多列,一個查詢條件可以占多個td

采用<table>的格式的格式,多行多列顯示,這樣更規則一些,更容易對齊。
如果有些控件比較長,比如時間的范圍查詢、多選組等,那么可以設置這些長控件多占用幾個td,在調整一下先后順序,整個頁面就可以比較好看,不會出現擠的擠死餓的餓死的情況。
這里“公司名稱”和“公司郵編”占用兩列(四個td),下面的日期查詢也占用了兩列(四個td),這樣整體結構比較緊湊,不會有浪費空間的感覺。

meta 驅動

字段(控件)需要的屬性都放在meta里面,做成單獨的json文件,用的時候加載進來就好,所以可以說——實現查詢,再也不用寫代碼了。

設計思路

根據查詢的特點,封裝下面幾個控件,順便把查詢方式歸納終結一下。再構思一下查詢數據如何存放的問題。

封裝基礎控件

基礎控件要比表單簡單一些,只需要文本、數字、日期、下拉選擇、單選組、多選組這幾個。其實單選組也可以變成下拉選擇的方式,只是想想有時候做成幾個圓圈圈的形式,選擇起來更方便一些。

查詢方式

主要就是等於、不等於、包含、范圍區間這幾種,只是不同的數據類型會有不同的拼接(查詢條件)方式,所以依據不同的數據類型就變成了這么多。
應該沒有漏掉的了。

查詢控件本身的屬性

查詢控件要設置顯示幾列,四列、五列、六列都行,看用戶顯示器有多寬了。
要設置快捷查詢用哪些查詢字段,還有用戶自己設置的個性化查詢方案。
這些用於生成table
后面的就是每個控件需要的meta數據了。

代碼

查詢子控件,比如文本類

<template>
  <div style="width:160px;" class="components-input-demo-presuffix">
    <a-input
      :id="'id' + meta.controlId"
      :name="'c' + meta.controlId"
      :value="value"
      :placeholder="meta.placeholder"
      :title="meta.title"
      :maxlength="meta.maxlength"
      :autocomplete="meta.autocomplete"
      :key="'ckey_'+meta.controlId"
      size="small"
      @input="myInput"
      >
        <template v-slot:addonBefore>
          <a-dropdown>
            <a class="ant-dropdown-link">{{kind}}</a>
            <template v-slot:overlay>
              <a-menu @click="handleMenuClick">
                <a-menu-item key="401">=</a-menu-item>
                <a-menu-item key="402">≠</a-menu-item>
                <a-menu-item key="403">含</a-menu-item>
              </a-menu>
            </template>
          </a-dropdown>
        </template>
    </a-input>
  </div>
</template>
<script>

export default {
  name: 'nf-find-input',
  model: {
    prop: 'modelValue',
    event: 'input'
  },
  props: {
    modelValue: String,
    meta: {
      type: Object,
      default: () => {
        return {
          controlId: Number, // 編號,區別同一個表單里的其他控件
          colName: String, // 字段名稱
          controlType: Number, // 用類型編號表示type
          placeholder: String,
          title: String, // 提示信息
          maxlength: Number, // 最大字符數
          autocomplete: { // off
            type: String,
            default: 'on'
          }
        }
      }
    }
  },
  data () {
    return {
      value: '',
      kind: '含',
      kindkey: '403',
      findKind: {
        401: '=', // 字符串
        402: '≠',
        403: '含',
        404: '不含',
        405: '起始',
        406: '結束',
        433: '在'
      }
    }
  },
  methods: {
    myInput: function (e) {
      this.value = e.target.value
      this.send()
    },
    handleButtonClick (e) {
      console.log('click left button', e)
    },
    handleMenuClick (e) {
      this.kindkey = e.key
      this.kind = this.findKind[e.key]
      console.log('click', e)
      this.send()
    },
    send: function () {
      var returnValue = []
      returnValue.push(this.kindkey)
      returnValue.push(this.value)
      var colName = this.meta.colName
      this.$emit('update:modelValue', returnValue) // 返回給調用者
      this.$emit('getvalue', returnValue, colName) // 返回給中間組件
    }
  }
}
</script>

這個比表單用的子控件要簡單很多。
其他的就不貼了,大同小異。

查詢控件

暫時沒有做成單獨的控件,之前是沒重構的代碼,現在用vue3.0的新方法重構了一下。

<template>
  <div class="home">
    <h1>查詢演示</h1>
    <div style="background-color:#dddddd;height:600px;width:100px;float:left;">
      <a href="#" @click="myClick('companyFind')">公司信息</a> <br>
      <a href="#" @click="myClick('personFind')">員工信息</a>
    </div>
    <div style="background-color:#eee;height:600px;width:1100px;float:left;">
      <!--快捷查詢,一行-->
      <div
        :style="{
          height: '70px',
          overflow: 'hidden',
          position: 'relative',
          border: '1px solid #ebedf0',
          borderRadius: '2px',
          padding: '2px',
          textAlign: 'left',
          background: '#fafafa',
        }"
      >
        <div class="ant-table ant-table-body ant-table-default ant-table-bordered" >
          <table role="all">
            <tbody class="ant-table-tbody">
              <tr>
                <td><!--個性化查詢方案-->
                  <a-dropdown size="small">
                    <template v-slot:overlay>
                      <a-menu @click="changeQuickFind">
                        <a-menu-item v-for="(item,key) in findMeta.customer" :key="key">
                          <UserOutlined />{{item.name}}
                        </a-menu-item>
                      </a-menu>
                    </template>
                    <a-button style="margin-left: 8px"> 快捷 <DownOutlined /> </a-button>
                  </a-dropdown>
                </td>
                <template v-for="key in quickFindKey" :key="key">
                  <td align="left" style="padding:3px 3px;height:20px">
                    <nfInput v-model="modelValue[getMeta(key).colName]"
                    :meta="findItem[key]" />
                  </td>
                </template>
                <td><a-button type="primary" @click="moreFindShow(true)">更多</a-button></td>
              </tr>
            </tbody>
          </table>
        </div>
      </div>
      <!--更多查詢條件,用抽屜打開-->
      <a-drawer
        title="更多查詢條件"
        placement="top"
        :closable="false"
        :visible="findVisible"
        @close="moreFindShow(false)"
      >
        <div class="ant-table ant-table-body ant-table-default ant-table-bordered" >
          <table role="all">
            <tbody class="ant-table-tbody">
              <template v-for="(tr, index) in findTable" :key="index"><!--循環行-tr-->
                <tr>
                  <template v-for="(td, index2) in tr" :key="index+'-'+index2"><!--循環列-td-->
                    <td align="right" style="padding:3px 3px;height:20px">
                      {{findItem[td].title}}:
                    </td>
                    <td :colspan="findItem[td].tdCount" align="left" style="padding:3px 3px;height:20px">
                      <nfInput v-model="modelValue[getMeta(td).colName]"
                      :meta="findItem[td]" />
                    </td>
                  </template>
                </tr><!--循環行-tr 結束 -->
              </template>
            </tbody>
          </table>
        </div>
      </a-drawer>
    </div>
  </div>
</template>

發現個問題 v-model="modelValue[getMeta(td).colName]" 這里的中括號不讓嵌套,所以只好寫個函數了。
這里就是兩套table,一個是單行的,用於快捷查詢,一個多多行多列的,用於顯示全部查詢條件。

meta格式

{
  "companyFind":{
    "findMeta":{
      "quickFind":[1009,1004,1005,1002],
      "allFind":[1000,1001,1002,1003,1009,1004,1005,1010,1006,1007,1011,1008],
      "colCount":4,
      "customer":{
        "100": {
          "name":"方案一1",
          "keys":[1009,1004,1005]
        },
        "101":{
          "name":"方案二2",
          "keys":[1006,1004,1005]
        }
      }
    },
    "findItem":{
      "1000":{
          "controlId": 1000,
          "colName": "companyName",
          "controlType": 101,
          "class": "",
          "placeholder": "公司名稱",
          "title": "公司名稱",
          "size": 10,
          "maxlength": 100,
          "optionList": [],
          "findKind":"402",
          "tdCount":1
      },
      "1001":{
          "controlId": 1001,
          "colName": "companyCode",
          "controlType": 131,
          "class": "",
          "placeholder": "公司郵編",
          "title": "公司郵編",
          "min": 100000,
          "max": 999999,
          "step": 1,
          "maxlength": 6,
          "optionList": [] ,
          "findKind":"401",
          "tdCount":1
      }
      ...... 更多查詢條件
    }
  }
}

有兩個主要部分,一個是對查詢本身進行描述,一個是對查詢條件的控件進行描述。

js代碼

首先定義一個管理函數(管理類)

// 查詢組件管理
const findManage = () => {
  // 渲染查詢條件,內容不會變,就不監控了
  const findWhere = {
    401: ' = "{k}"',
    402: ' <> "{k}"',
    403: ' like "%{k}%"',
    404: ' not like "%{k}%"',
    405: ' like "{k}%"',
    406: ' like "%{k}"',
    411: ' ={k}',
    412: ' <>{k}',
    413: ' >{k}',
    414: ' >={k}',
    415: ' <{k}',
    416: ' <={k}',
    421: ' ="{k}"',
    431: ' between {k1} and {k2}',
    432: ' between "{k1}" and "{k2}" ',
    433: ' in ({k})'
  }
  // 加載meta信息,json格式
  const json = require('./FindDemo.json')
  const findMeta = ref({}) // 查詢表單的meta信息
  const findItem = ref({}) // 表單需要的meta信息
  const modelValue = ref({}) // 放數據的model
  const quickFindKey = ref({}) // 存放快捷查詢的key
  const findTable = ref([]) // 二維數組存放meta的ID
  // 把查詢里的字段,變成多行多列的分布,二維數組
  const metaToTable = () => {
    var tdCount = 0
    var td = []
    findTable.value = []
    for (var index in findMeta.value.allFind) { // 遍歷設定的meta的key的數組
      var key = findMeta.value.allFind[index]
      var meta = findItem.value[key]
      td.push(key)
      tdCount += 1 + meta.tdCount
      if (tdCount >= findMeta.value.colCount * 2) {
        findTable.value.push(td)
        td = []
        tdCount = 0
      }
    }
    if (td.length > 0) {
      findTable.value.push(td)
    }
  }
  // 更換個性化查詢方案
  const changeQuickFind = (e) => {
    quickFindKey.value = findMeta.value.customer[e.key].keys
  }
  // 切換其他查詢模塊
  const myClick = (key) => {
    // 更換表單的meta
    findMeta.value = json[key].findMeta
    findItem.value = json[key].findItem
    // 加載快捷查詢
    quickFindKey.value = json[key].findMeta.quickFind
    // 初始化
    modelValue.value = {}
    // 創建model
    modelValue.value = {}
    for (var k in findItem.value) {
      var item = findItem.value[k]
      modelValue.value[item.colName] = ''
    }

    metaToTable()
  }

  // 抽屜的事件
  const findVisible = ref(false)
  const moreFindShow = (isShow) => {
    findVisible.value = isShow
  }
  return {
    modelValue,
    findMeta,
    findItem,
    quickFindKey,
    findWhere,
    findTable,
    myClick,
    findVisible,
    moreFindShow
  }
}

首先定義需要的數據,然后定義事件,最后通過return 的方式暴露出來共用的數據和方法。

然后在setup里面引用就可以了,這樣相同功能的代碼可以寫在一起,setup只是一個統一調度的地方,代碼可以清洗很多。

setup () {
    // 引入查詢管理
    const { modelValue, findMeta, findItem, findWhere, quickFindKey, myClick, changeQuickFind, findTable, findVisible, moreFindShow } = findManage()
    myClick('companyFind')
    // 通過key獲取meta
    const getMeta = (td) => {
      return findItem.value[td]
    }
    return {
      findVisible,
      moreFindShow,
      changeQuickFind,
      modelValue,
      findItem,
      findMeta,
      quickFindKey,
      findWhere,
      findTable,
      myClick,
      getMeta
    }
  }

一般一個“頁面”里不會只有一個查詢,至少也得來個數據列表+分頁才行,同理數據列表的相關代碼也可以單獨寫個管理類,這樣setup里面只是調用這些類,可讀性會顯著提高。

感想

Ant Design Vue,研究了幾天,感覺有個強大的UI庫,太方便了。其實以前就一直想做這種方式的查詢控件,但是css很爛,一些效果做不出來,比如抽屜形式的更多查詢條件、查詢方式的切換、個性化方案的選擇等。
日期控件太復雜了,研究了好幾天還是沒用研究透,還需要繼續專研,因為關於日期時間查詢的地方還有一些細節沒有實現好。

one more thing

后面就是數據列表、分頁。然后一個模塊的增刪改查就全了。
又研究了一下ant design vue pro。好多東西都不用自己再寫了,輕松很多,然后就是把自己的想法注入進去了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM