【React/Vue2】 使用Element UI 高度封装Select 下拉框创建条目(Ant Design更为简单)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 【React/Vue2】 使用Element UI 高度封装Select 下拉框创建条目(Ant Design更为简单)

theme: fancy

个人笔记,如有错误请谅解。

需求

所有需求大多数用于展示目的,真实值不被修改。

主要需求

  1. 用户输入正整数例如 1-4 则自动选择option里的1,2,3,4。label展示为1-4 (数字连续要分开,例如 1,2,3 6,7,8)
  2. 用户手动单击选择option里的1,2,3,4。label展示为1-4
  3. 服务端返回[1,2,3,4],label展示为1-4
  4. 处理数据错误。例如用户输入非法字符或格式错误等10-4、xx-3、好-我、1---3 可选option只有1,2,4,6 结果用户输入1-7等...
  5. 兼容程序。
  6. 回车空格 均作为确认快捷键。
  7. 删除要做二次确认,和选中效果。
  8. 用户输入快捷方式的时候,并不能影响到原option数据展示。

效果图

1.png

以下代码均做简化处理,只保留重要部分。

先简单的理一下思路,我们只需要做到表面上的修改甚至可以直接使用jQuery来操纵真实dom。
用户输入,以及v-model的内容,我们都处理一遍就好了。对我个人来讲,v-model对这种需求并不友好。我还是喜欢
React /(ㄒoㄒ)/~~


首先加载组件

<!--oldOptions 是定义在date里的变量数据-->
<!-- Vue-->
   <el-select
        v-model="dataModel"
        allow-create
        default-first-option
        automatic-dropdown
        :multiple="true"
        :filterable="true"
        :fit-input-width="true"
        :filter-method="(value) => do_not_searchFillterData(value, oldOptions)"
        @change="(e)=>{selectValue_changle(this,e)}"
        @focus="init_input_keyup"
      >
        <el-option
          v-for="item in do_not_searchInit(formData.options.options)"
          :key="item.value"
          :value="item.value"
          :label="item.value"
        >

          <span>{
  
  {item.value }}</span>
        </el-option>
      </el-select>


<!--React-->
  <Select
     mode="tags"
     options={options}
     onChange={(e)=>{selectValue_changle(this,e)}}
     filterOption={(input, option) =>{do_not_searchInit(input,oldOptions)}}
     onFocus={init_input_keyup}
  />

就从上面结构代码来挨个挨个解释吧。
multiple、filterable、default-first-option必须为true这是创建条目,多选的必备属性。
filter-method 这个属性里我们使用了do_not_searchFillterData方法目的是解决需求 8 用户输入快捷...

下面方法很简单,既然不要影响options里的数据,那么我们直接不做处理,返回原数组就好了。

do_not_searchInit

    //----------【创建条目组件】解决下拉框展示当前输入内容,而不是可选值---------
    //Vue 
    do_not_searchInit(e) {
   
   
      this.oldOptions = e
      return e;
    }
    //React 
    const [oldOptions,setOldOptions]=useState([])
    const do_not_searchInit=(e)=>{
   
   
      setOldOptions(e)
      return e;
    }

onChange 这个我们需要处理的就比较多了,解决需求 1、2、3、4、5 因为这是我们功能的主要方法,完成了他,基本就写完了。

下面代码我们首先进行校验,使用正则表达式,让用户只能输入正整数-正整数 表达式为/^([0-9]+-[0-9]+)$/ ,然后我们处理好其他校验后,进行数据处理,分为两种处理方式。

方式一、用户输入xx-xx

方式二、用户选择数字

我直接用最简单最笨的方法,使用includes 进行判断是否存在字符串- 如果存在,走方式一

然后把拿到的例如1-3 转换成1,2,3 Array map结合起来使用 ,然后我们得数组[1,2,3] 别着急,我们还要进行一次数据处理,要把不连续的数组分开,排序,输出为[[1,2,3],[7,8,9]] 这个时候就通过我们的sortArr方法了。

selectValue_changle

// Vue
data() {
   
   
  return {
   
   
    dataModel: [],
  }
selectValue_changle(el, val) {
   
   
  // console.log('入口---', val);
  //error:所选范围包含无效值 ↓
  //可选范围校验
  //假如可选数组为[1,4,5] 而用户输入1-4 解析出来就是[1,2,3,4]
  //通过 includes来校验 用户输入数值,通过可选数组校验后,是否含有不存在值。

  //error:数值大小有误 ↓
  //数值大小校验
  //例如:10-1  可选数值为1 用户输入 大于1的任何数等

  //error:数值表达式有误 ↓
  //基本的格式校验
  //通过正则匹配符合条件的格式
  //例如 Number-Number
  // val=$.trim(val.at(-1));

  if (this.formData.options.options.length >= 1) {
   
   
    // console.log('测试入口---', val);

    if (val.at(-1) > this.formData.options.options.at(-1).value) {
   
   
      console.error('数值大小有误')
      this.dataModel.length = this.dataModel.length - 1
      return;
    }
    if (val.length >= 1 && val.at(-1).toString().includes('-')) {
   
   
      // console.log('表达式入口---', val, val.at(-1).toString().includes('-'));
      //1-3 转换 1,2,3
      const newVal = val.at(-1).split('-')
      const start = parseInt(newVal[0]);
      const end = parseInt(newVal[1]);
      const regE = /^([0-9]+-[0-9]+)$/; //校验格式xx-xx
      const regES = new RegExp(regE);
      if (regES.test(val.at(-1)) && start < end) {
   
   
        const inveres = Array(end - start + 1).fill(0).map((v, i) => i + start)
        const visibleArraydata = []//可选数据
        for (let is = 0; is < this.formData.options.options.length; is++) {
   
   
          const item = this.formData.options.options[is];
          visibleArraydata.push(item.value)
        }
        if (inveres.filter(item => !visibleArraydata.includes(item)).length > 0) {
   
   
          console.error('所选范围包含无效值')
          this.dataModel.length = this.dataModel.length - 1
          return;
        }
        if (inveres[0] < this.formData.options.options[0].value) {
   
   
          console.error('数值大小有误')
          this.dataModel.length = this.dataModel.length - 1
          return;
        }
        if (inveres.at(-1) > this.formData.options.options.at(-1).value) {
   
   
          console.error('数值大小有误')
          this.dataModel.length = this.dataModel.length - 1
          return;
        } else {
   
   
          //
          if (this.dataModel.length === 0) {
   
   
            this.dataModel = []
          } else {
   
   
            this.dataModel.length = this.dataModel.length - 1
          }
          this.dataModel.push(...inveres)
          // this.create_dom_node(el, this.dataModel)

          this.create_dom_node(el, this.sortArr(this.dataModel))
        }
      } else {
   
   
        console.error('数值表达式有误')
        this.dataModel.length = this.dataModel.length - 1
        return;
      }
    } else {
   
   
      // console.log('选择入口---', val);

      this.create_dom_node(el, this.sortArr(val))
      // if (val.at(-1)> this.formData.options.options.at(-1).value) {
   
   
      //   openMsg(this, '数值大小有误', 'error')
      //   this.dataModel.length=this.dataModel.length-1
      //   return;
      // } else {
   
   

      // }
    }
  } else {
   
   
    console.error('数值大小有误')
    this.dataModel.length = this.dataModel.length - 1
    return;
  }
}

// React
  const [dataModel,setDataModel]=useState([])
  const selectValue_changle = (el, val) => {
   
   
    // console.log('入口---', val);
    //error:所选范围包含无效值 ↓
    //可选范围校验
    //假如可选数组为[1,4,5] 而用户输入1-4 解析出来就是[1,2,3,4]
    //通过 includes来校验 用户输入数值,通过可选数组校验后,是否含有不存在值。

    //error:数值大小有误 ↓
    //数值大小校验
    //例如:10-1  可选数值为1 用户输入 大于1的任何数等

    //error:数值表达式有误 ↓
    //基本的格式校验
    //通过正则匹配符合条件的格式
    //例如 Number-Number
    // val=$.trim(val.at(-1));

    if (formData.options.options.length >= 1) {
   
   
      // console.log('测试入口---', val);

      if (val.at(-1) > formData.options.options.at(-1).value) {
   
   
        console.error('数值大小有误')
        dataModel.length = dataModel.length - 1
        return;
      }
      if (val.length >= 1 && val.at(-1).toString().includes('-')) {
   
   
        // console.log('表达式入口---', val, val.at(-1).toString().includes('-'));
        //1-3 转换 1,2,3
        const newVal = val.at(-1).split('-')
        const start = parseInt(newVal[0]);
        const end = parseInt(newVal[1]);
        const regE = /^([0-9]+-[0-9]+)$/; //校验格式xx-xx
        const regES = new RegExp(regE);
        if (regES.test(val.at(-1)) && start < end) {
   
   
          const inveres = Array(end - start + 1).fill(0).map((v, i) => i + start)
          const visibleArraydata = []//可选数据
          for (let is = 0; is < formData.options.options.length; is++) {
   
   
            const item = formData.options.options[is];
            visibleArraydata.push(item.value)
          }
          if (inveres.filter(item => !visibleArraydata.includes(item)).length > 0) {
   
   
            console.error('所选范围包含无效值')
             setDataModel.length - 1
            return;
          }
          if (inveres[0] < formData.options.options[0].value) {
   
   
            console.error('数值大小有误')
             setDataModel.length - 1
            return;
          }
          if (inveres.at(-1) > formData.options.options.at(-1).value) {
   
   
            console.error('数值大小有误')
         setDataModel.length - 1
            return;
          } else {
   
   
            //
            if (dataModel.length === 0) {
   
   
              setDataModel([])
            } else {
   
   
             setDataModel.length - 1
            }
            setDataModel(...inveres)
            // create_dom_node(el, dataModel)

            create_dom_node(el, sortArr(dataModel))
          }
        } else {
   
   
          console.error('数值表达式有误')
          setDataModel.length - 1
          return;
        }
      } else {
   
   
        // console.log('选择入口---', val);

        create_dom_node(el, sortArr(val))
        // if (val.at(-1)> formData.options.options.at(-1).value) {
   
   
        //   openMsg(this, '数值大小有误', 'error')
        //   dataModel.length=dataModel.length-1
        //   return;
        // } else {
   
   

        // }
      }
    } else {
   
   
      console.error('数值大小有误')
      setDataModel.length - 1
      return;
    }
  }

sortArr 数据处理为 [[1,2,3],[7,8,9]]

这边把数据排序处理为指定格式,是为了方便我们后期使用,处理完成后,直接使用create_dom_node(el, sortArr(dataModel))方法 进行dom的绘制处理. (el 是上方的dom节点,this)

// Vue
sortArr(res) {
   
   
  if (res.length >= 1) {
   
   
    //1,2,3 转换 1-3
    var arrays = []
    for (let i = 0; i < this.turnNum(res).length; i++) {
   
   
      const it = this.turnNum(res)[i]
      if (!isNaN(parseInt(it))) {
   
   
        arrays.push(parseInt(it))
      } else {
   
   
        console.error('数值格式有误')

        this.dataModel.length = this.dataModel.length - 1
        return;
      }
    }
    arrays = Array.from(new Set(arrays))
    const result = [];
    let i = 0;
    const list = arrays.sort((a, b) => a - b);
    list.forEach((item, index) => {
   
   
      if (index === 0) {
   
   
        result[0] = [item];
      } else if (item - list[index - 1] === 1) {
   
    // 判断当前值 和 前一个值是否相差1
        result[i].push(item);
      } else {
   
   
        result[++i] = [item]; // 开辟新空间。
      }
    })
    this.dataModel = Array.from(new Set(result.flat()))

    return result;
  }
}
// React

const sortArr = (res) => {
   
   
  if (res.length >= 1) {
   
   
    //1,2,3 转换 1-3
    let arrays = []
    for (let i = 0; i < this.turnNum(res).length; i++) {
   
   
      const it = this.turnNum(res)[i]
      if (!isNaN(parseInt(it))) {
   
   
        arrays.push(parseInt(it))
      } else {
   
   
        console.error('数值格式有误')

        setDataModel.length - 1
        return;
      }
    }
    arrays = Array.from(new Set(arrays))
    const result = [];
    let i = 0;
    const list = arrays.sort((a, b) => a - b);
    list.forEach((item, index) => {
   
   
      if (index === 0) {
   
   
        result[0] = [item];
      } else if (item - list[index - 1] === 1) {
   
    // 判断当前值 和 前一个值是否相差1
        result[i].push(item);
      } else {
   
   
        result[++i] = [item]; // 开辟新空间。
      }
    })
    setDataModel(Array.from(new Set(result.flat())))
    return result;
  }
}

绘制DOM create_dom_node

这边写的最多的无非就是 使用jQuery选中我要操作的节点,进行add增加标签而已。因为我的结构较多,所以写的就比较多。如图。

2.png

//Vue
create_dom_node(el, vals, isDisplay) {
   
   
  // console.log('create_dom_node 拿到解析的值', vals);
  const visibleArraydata = []//可选数据
  for (let is = 0; is < this.formData.options.options.length; is++) {
   
   
    const item = this.formData.options.options[is];
    visibleArraydata.push(item.value)
  }
  if (vals) {
   
   
    if (vals.at(-1).filter(item => !visibleArraydata.includes(item)).length > 0 && !isDisplay) {
   
   
      // console.log(vals, '所选范围包含无效值')
      openMsg(this, '所选范围包含无效值', 'error')
      this.dataModel.length = this.dataModel.length - 1
      return;
    }

    const _this = this
    {
   
   mathJaxContainer[0]}el).find('.el-select__tags span').remove()
    for (let i = 0; i < vals.length; i++) {
   
   
      const item = vals[i];
      var tagStr = ''
      if (item.length > 1) {
   
   
        tagStr = item[0] + '-' + item[item.length - 1];
      } else {
   
   
        tagStr = item[0];
      }
      // this.dataModel=vals.flat()
      const ptag = '<span class="el-tag el-tag--info el-tag--mini el-tag--light"><span class="el-select__tags-text">' + tagStr + '</span> <i class="el-tag__close el-icon-close"></i> </span>';
      const spana = $('<span value="' + JSON.stringify(item) + '">' + ptag + '</span>');

      spana.find('i').click(function (e) {
   
   
        var span = $(this).parents('.el-tag--light').parents('span')
        if (span.length > 0) {
   
   
          var value = span.attr('value');
          $(this).parent().remove();
          _this.getNewDataModel(_this.dataModel, value)
        }
        e.stopPropagation();
        {
   
   mathJaxContainer[1]}el).find('.el-select__tags input').focus();
        return false;
      })
      {
   
   mathJaxContainer[2]}el).find('.el-select__tags').append(spana)
      {
   
   mathJaxContainer[3]}el).find('.el-select__tags input').appendTo({
   
   mathJaxContainer[4]}el).find('.el-select__tags'))
      if ($('.el-tag--light').length >= 2) {
   
   
        this.isDelete = true
      }
    }
  } else {
   
   
    {
   
   mathJaxContainer[5]}el).find('.el-select__tags span').remove()
  }
},
//React
const create_dom_node = (el, vals, isDisplay) => {
   
   
  // console.log('create_dom_node 拿到解析的值', vals);
  const visibleArraydata = []//可选数据
  for (let is = 0; is < formData.options.options.length; is++) {
   
   
    const item = formData.options.options[is];
    visibleArraydata.push(item.value)
  }
  if (vals) {
   
   
    if (vals.at(-1).filter(item => !visibleArraydata.includes(item)).length > 0 && !isDisplay) {
   
   
      // console.log(vals, '所选范围包含无效值')
      openMsg(this, '所选范围包含无效值', 'error')
      dataModel.length = dataModel.length - 1
      return;
    }

    {
   
   mathJaxContainer[6]}el).find('.el-select__tags span').remove()
    for (let i = 0; i < vals.length; i++) {
   
   
      const item = vals[i];
      var tagStr = ''
      if (item.length > 1) {
   
   
        tagStr = item[0] + '-' + item[item.length - 1];
      } else {
   
   
        tagStr = item[0];
      }
      // dataModel=vals.flat()
      const ptag = '<span class="el-tag el-tag--info el-tag--mini el-tag--light"><span class="el-select__tags-text">' + tagStr + '</span> <i class="el-tag__close el-icon-close"></i> </span>';
      const spana = $('<span value="' + JSON.stringify(item) + '">' + ptag + '</span>');

      spana.find('i').click(function (e) {
   
   
        var span = $(this).parents('.el-tag--light').parents('span')
        if (span.length > 0) {
   
   
          var value = span.attr('value');
          $(this).parent().remove();
          _getNewDataModel(_dataModel, value)
        }
        e.stopPropagation();
        {
   
   mathJaxContainer[7]}el).find('.el-select__tags input').focus();
        return false;
      })
      {
   
   mathJaxContainer[8]}el).find('.el-select__tags').append(spana)
      {
   
   mathJaxContainer[9]}el).find('.el-select__tags input').appendTo({
   
   mathJaxContainer[10]}el).find('.el-select__tags'))

    }
  } else {
   
   
    {
   
   mathJaxContainer[11]}el).find('.el-select__tags span').remove()
  }
},

本文同步 我的笔记

End

相关文章
|
2月前
|
前端开发 数据可视化 JavaScript
🚀打造卓越 UI:2024 年不容错过的 9 个 React UI 组件库✨
本文介绍了2024年最受欢迎的9个React UI组件库,每一个都在设计、功能和定制化上有独特的优势,包括Material UI、Ant Design、Chakra UI等。这些组件库为开发者提供了强大、灵活的工具,可以帮助构建现代化、无障碍且高效的Web应用程序。文章详细分析了每个库的特点、适用场景以及关键功能,帮助开发者在项目中做出最合适的选择,无论是打造企业级仪表板还是时尚的用户界面。
182 6
🚀打造卓越 UI:2024 年不容错过的 9 个 React UI 组件库✨
|
1月前
|
缓存 前端开发 开发者
深入理解React Hooks,打造高效响应式UI
深入理解React Hooks,打造高效响应式UI
33 0
|
3月前
|
JavaScript
Vue使用element中table组件实现单选一行
如何在Vue中使用Element UI的table组件实现单选一行的功能。
190 5
Vue使用element中table组件实现单选一行
|
3月前
|
前端开发 JavaScript
React技术栈-React UI之ant-design使用入门
关于React技术栈中使用ant-design库的入门教程,包括了创建React应用、搭建开发环境、配置按需加载、编写和运行代码的步骤,以及遇到错误的解决方法。
48 2
React技术栈-React UI之ant-design使用入门
|
3月前
|
资源调度 JavaScript 前端开发
使用vite+react+ts+Ant Design开发后台管理项目(二)
使用vite+react+ts+Ant Design开发后台管理项目(二)
|
2月前
|
资源调度 JavaScript PHP
Vue3+ element plus 前后分离admin项目安装教程
Vue3+ element plus 前后分离admin项目安装教程
63 0
|
3月前
|
传感器 监控 前端开发
WHAT - 通过 react-use 源码学习 React(UI 篇)
WHAT - 通过 react-use 源码学习 React(UI 篇)
|
4月前
|
计算机视觉
ElementUI——vue2+element-ui 2.x的动态表格和表单
ElementUI——vue2+element-ui 2.x的动态表格和表单
169 1
|
4月前
|
JavaScript
Element - Vue使用slot-scope和v-for遍历数据为树形表格
这篇文章介绍了在Vue中使用`slot-scope`和`v-for`指令来遍历数据并将其渲染为树形表格的方法。
70 0
Element - Vue使用slot-scope和v-for遍历数据为树形表格
|
4月前
|
开发者 C# Android开发
明白吗?Xamarin与Native的终极对决:究竟哪种开发方式更适合您的项目需求,让我们一探究竟!
【8月更文挑战第31天】随着移动应用开发的普及,开发者面临多种技术选择。本文对比了跨平台解决方案Xamarin与原生开发方式的优势与劣势。Xamarin使用C#进行跨平台开发,代码复用率高,可大幅降低开发成本;但因基于抽象层,可能影响性能。原生开发则充分利用平台特性,提供最佳用户体验,但需维护多套代码库,增加工作量。开发者应根据项目需求、团队技能和预算综合考量,选择最适合的开发方式。
131 0