打造一款適合本身的快速開發框架-前端篇之下拉組件設計與實現

前言

相信使用過elementui的同窗確定也使用過Select選擇器組件,同時也確定使用過其遠程搜索模式。在開啓遠程搜索後,能夠根據輸入的關鍵字去後端獲取列表,而後進行選擇。本文說的下拉組件其實也是elementui的Select選擇器組件,不過會對其進一步封裝,以方便業務系統使用。前端

下拉組件設計

咱們先簡單分析一下下拉組件應該具有的功能:vue

先分析一下使用場景

使用到下拉組件的狀況通常是須要選擇的數據來源於另外一個接口,好比添加用戶的時候,須要選擇角色。此時數據源就來自角色列表接口。這裏簡單講一下可能存在的狀況:git

  1. 選擇單條記錄
  2. 修改模式下選擇單條記錄
  3. 選擇多條記錄
  4. 修改模式下選擇多條記錄

修改模式的場景算是一種比較特殊的場景,須要進行以下處理:json

  1. 已選擇的記錄須要在當前頁中能看到;
  2. 如是多條記錄,可對當前選中的記錄進行增/減

接口說明

爲了儘量的複用接口,這裏依然使用的是後端默認查詢的列表接口如:sys/role/listsys/user/user等。下面以用戶列表爲例:後端

請求地址api

{{api_base_url}}/sys/user/userbash

數據類型app

application/json框架

請求示例:異步

選擇單條記錄

{
    "m_LIKE_userName",""
}
====>由src/utils.request.js全局請求數據處理進行轉換
{
	"whereParams": [{
		"operateType": "LIKE",
		"propertyName": "userName",
		"propertyValue": ""
	}]
}
複製代碼

修改模式下選擇單條記錄,每次查詢都要返回當前id的記錄

{
    "m_LIKE_userName","",
    "mor_EQ_id": 1
}
====>由src/utils.request.js全局請求數據處理進行轉換
{
	"whereParams": [{
		"operateType": "LIKE",
		"propertyName": "userName",
		"propertyValue": ""
	}, {
		"operateType": "OR",
		"propertyName": "id",
		"propertyValue": {
			"operateType": "EQ",
			"propertyName": "id",
			"propertyValue": 1
		}
	}]
}
複製代碼

選擇多條記錄

{
    "m_LIKE_userName",""
}
====>由src/utils.request.js全局請求數據處理進行轉換
{
	"whereParams": [{
		"operateType": "LIKE",
		"propertyName": "userName",
		"propertyValue": ""
	}]
}
複製代碼

修改模式下選擇多條記錄,每次查詢都要返回當前id的記錄

{
    "m_LIKE_userName","",
    "mor_EQ_id": [1, 2]
}
====>由src/utils.request.js全局請求數據處理進行轉換
{
	"whereParams": [{
		"operateType": "LIKE",
		"propertyName": "userName",
		"propertyValue": ""
	}, {
		"operateType": "OR",
		"propertyName": "id",
		"propertyValue": {
			"operateType": "EQ",
			"propertyName": "id",
			"propertyValue": [1,2]
		}
	}]
}
複製代碼

響應示例:

{
	"code": 0,
	"msg": "查詢用戶成功",
	"data": {
		"pageNum": 1,
		"pageSize": 15,
		"recordCount": 4,
		"totalPage": 1,
		"rows": [{
			"id": 1,
			"userName": "admin",
			"realName": "mldong",
			"avatar": "",
			"email": "",
			"mobilePhone": "18676163666",
			"telephone": "",
			"password": "52618c88aa68c63d37e50d6acd8b8456",
			"salt": "v7hc7v69",
			"sex": 1,
			"isLocked": 1,
			"createTime": "2020-06-09 21:47:33",
			"updateTime": "2020-06-24 10:11:03",
			"isDeleted": 1
		}, {
			"id": 9,
			"userName": "ni",
			"realName": "大的",
			"email": "8551312@163.com",
			"mobilePhone": "13023123256",
			"password": "c079872794690156289617a60a11c316",
			"salt": "ztih3jzc",
			"sex": 2,
			"isLocked": 1,
			"createTime": "2020-06-22 10:01:12",
			"updateTime": "2020-06-22 10:04:46",
			"isDeleted": 1
		}, {
			"id": 10,
			"userName": "地方v的",
			"realName": "hghg",
			"email": "13696452586@163.com",
			"mobilePhone": "13645607895",
			"password": "d9acbc2eef540ffc8ea6dd8fdacd37cc",
			"salt": "jpqocubh",
			"sex": 2,
			"isLocked": 1,
			"createTime": "2020-06-22 10:06:41",
			"updateTime": "2020-06-22 10:06:41",
			"isDeleted": 1
		}, {
			"id": 11,
			"userName": "孫狗",
			"realName": "孫笑川",
			"mobilePhone": "17444444444",
			"password": "7941882c9fdcd4bb2a17b804d2b5c5ba",
			"salt": "ewst066f",
			"sex": 1,
			"isLocked": 1,
			"createTime": "2020-06-22 15:03:06",
			"updateTime": "2020-06-22 15:03:06",
			"isDeleted": 1
		}]
	}
}
複製代碼

組件參數說明

暫時定幾個經常使用的參數,後續可能還會有追加

參數名 類型 默認值 說明
valueKey String id 列表中選項的值對應的key
labelKey String name 列表中選項的值對應的key
searchKey String name 模糊搜索的key
url String undefined 接口地址
isEdit Boolean false 是否編輯模式
value String, Number, Array undefined 綁定的值
multiple Boolean false 是否多選
size String medium 組件大小medium/small/mini
placeholder String 請選擇 佔位符

開始編碼

目錄結構

├── src
	├──	components/m
		├──	Select
			└── index.vue
	├── utils
		└── request.js
	├── views
		├──	dashboard
			└── index.vue
	└── main.js
複製代碼

文件詳解

  • src/components/m/Select/index.vue

下拉組件

<template>
  <div class="m-select">
    <!--el-input readonly :size="size" :placeholder="placeholder" v-model="mValue">
      <el-button slot="append" icon="el-icon-search"></el-button>
    </el-input-->
    <el-select
      :size="size"
      filterable
      :multiple="multiple"
      remote
      :loading="loading"
      :remote-method="requestData"
      :placeholder="placeholder"
      @change="handleChange"
      v-model="mValue">
      <el-option
        v-for="item in options"
        :key="item[valueKey]"
        :label="item[labelKey]"
        :value="item[valueKey]">
        <slot v-bind:option="item"> </slot>
      </el-option>
    </el-select>
  </div>
</template>
<script>
import request from '@/utils/request'
export default {
  name: 'MSelect',
  props: {
    valueKey: { // 列表中選項的值對應的key
      type: String,
      default: 'id'
    },
    labelKey: { // 列表中選項的標籤對應的key
      type: String,
      default: 'name'
    },
    searchKey: { // 模糊搜索的key
      type: String,
      default: 'name'
    },
    url: { // 接口地址
      type: String,
      default: undefined
    },
    isEdit: { // 是否編輯模式
      type: Boolean,
      default: false
    },
    // 綁定的值
    value: {
      type: [String, Number, Array],
      default: undefined
    },
    multiple: { // 是否多選
      type: Boolean,
      default: false
    },
    size: { // medium/small/mini
      type: String,
      default: 'medium'
    },
    placeholder: { //  佔位符
      type: String,
      default: '請選擇'
    }
  },
  data() {
    return {
      loading: false,
      mValue: undefined,
      options: []
    }
  },
  watch: {
    value(n, o) { // 監聽父組件值變更,子組件也要變更
      this.mValue = n
      if ((o === undefined && this.isEdit) || (o !== undefined && o.length === 0 && this.isEdit)) {
        // 若是舊的值等於undefined
        this.requestData('')
      }
    }
  },
  created() {
    if (!this.isEdit) {
      this.requestData('')
    }
  },
  methods: {
    // 請求數據
    requestData(k) {
      if (this.url) {
        this.loading = true
        var operateType = this.multiple ? 'IN' : 'EQ'
        request({
          url: this.url,
          method: 'post',
          data: {
            ['m_LIKE_' + this.searchKey]: k,
            [`mor_${operateType}_` + this.valueKey]: this.value
          }
        }).then(res => {
          this.loading = false
          if (res.code === 0) {
            this.options = res.data.rows
          }
        }).catch(() => {
          this.loading = false
        })
      }
    },
    // 子組件值變化要經過父組件
    handleChange(value) {
      this.$emit('input', value)
    }
  }
}
</script>

複製代碼
  • src/utils/request.js

新增or的全局處理,代碼片斷

// request interceptor
service.interceptors.request.use(
  config => {
    // do something before request is sent

    if (store.getters.token) {
      // 存在token,就放到請求頭中
      // 這裏修改一下請求頭與後端一致,X-Token->Auth-Token
      config.headers['Auth-Token'] = getToken()
    }
    if (config.data) {
      // 這裏對全局的請求參數作處理,主要是拼接查詢條件
      var whereParams = []
      Object.keys(config.data).forEach(item => {
        if (item.startsWith('m_')) {
          var value = config.data[item]
          if (value !== undefined) {
            var arr = item.split('_')
            if (arr.length === 3) {
              whereParams.push({
                operateType: arr[1],
                propertyName: arr[2],
                propertyValue: value
              })
            }
          }
          delete config.data[item]
        } else if (item.startsWith('mor_')) {
          // 處理簡單的or語句
          value = config.data[item]
          arr = item.split('_')
          if (value !== undefined) {
            if (arr.length === 3) {
              whereParams.push({
                operateType: 'OR',
                propertyName: arr[2],
                propertyValue: {
                  operateType: arr[1],
                  propertyName: arr[2],
                  propertyValue: value
                }
              })
            }
          }
          delete config.data[item]
        }
      })
      if (whereParams.length) {
        config.data.whereParams = whereParams
      }
    }

    return config
  },
  error => {
    // do something with request error
    console.log(error) // for debug
    return Promise.reject(error)
  }
)
複製代碼
  • src/main.js

主入口全局註冊自定義組件,這裏也用了require.context,代碼片斷

import Vue from 'vue'

// 處理自定義組件全局註冊
const files = require.context('./components/m', true, /\.vue$/)
files.keys().forEach((routerPath) => {
  const componentName = routerPath.replace(/^\.\/(.*)\/index\.\w+$/, '$1')
  const value = files(routerPath)
  Vue.component('m-' + componentName.toLowerCase(), value.default)
}, {})
複製代碼
  • src/views/dashboard/index.vue

這裏提供了使用樣例:

選擇單個

<m-select v-model="userId" url="sys/user/list" value-key="id" label-key="userName" search-key="userName"></m-select>
複製代碼

選擇單個-修改模式

<m-select is-edit v-model="form.userId" url="sys/user/list" value-key="id" label-key="userName" search-key="userName"></m-select>
複製代碼

選擇多個

<m-select multiple v-model="userIds" url="sys/user/list" value-key="id" label-key="userName" search-key="userName"></m-select>
複製代碼

選擇多個-修改模式

<m-select multiple is-edit v-model="form.userIds" url="sys/user/list" value-key="id" label-key="userName" search-key="userName"></m-select>
複製代碼

自定義佈局

<m-select v-model="userId" url="sys/user/list" value-key="id" label-key="userName" search-key="userName">
    <template v-slot:default="{ option }">
		<span>{{ option.id }}--{{ option.userName }}</span>
    </template>
</m-select>
複製代碼

js片斷

export default {
  name: 'Dashboard',
  data() {
    return {
      form: {
        userId: undefined,
        userIds: []
      },
      userId: undefined,
      userIds: []
    }
  },
  created() {
    // 模擬修改異步更新
    setTimeout(() => {
      this.$set(this.form, 'userId', 1)
      this.$set(this.form, 'userIds', [1, 9])
    }, 2000)
  }
}
複製代碼

效果圖

小結

本文的下拉組件仍是很粗淺的封裝,目前的交互也是根據本身的操做習慣去作的,除了該種作法外,還有一種作法是彈出框的方式,後續作到下拉菜單樹的時候會考慮使用該種方式。

項目源碼地址

  • 後端

gitee.com/mldong/mldo…

  • 前端

gitee.com/mldong/mldo…

相關文章

打造一款適合本身的快速開發框架-先導篇

打造一款適合本身的快速開發框架-前端腳手架搭建

打造一款適合本身的快速開發框架-前端篇之登陸與路由模塊化

打造一款適合本身的快速開發框架-前端篇之框架分層及CURD樣例

打造一款適合本身的快速開發框架-前端篇之字典組件設計與實現

相關文章
相關標籤/搜索