vue 使用饿了么UI仿写teambition的筛选功能

2022-04-15 0 1,054
目录
  • 问题描述
    • 大致的功能效果有如下
  • 思路分析
    • 完整代码
      • 总结

        问题描述

        teambition软件是企业办公协同软件,相信部分朋友的公司应该用过这款软件。里面的筛选功能挺有意思,本篇文章,就是仿写其功能。我们先看一下最终做出来的效果图

        vue 使用饿了么UI仿写teambition的筛选功能

        大致的功能效果有如下

        • 需求一:常用筛选条件放在上面直接看到,不常用筛选条件放在添加筛选条件里面
        • 需求二:筛选的方式有输入框筛选、下拉框筛选、时间选择器筛选等
        • 需求三:如果觉得常用筛选条件比较多的话,可以鼠标移入点击删除,使之进入不常用的筛选条件里
        • 需求四:也可以从不常用的筛选条件里面点击对应筛选条件使之“蹦到”常用筛选条件里
        • 需求五:点击重置使之恢复到初试的筛选条件
        • 需求六:用户若是没输入内容点击确认按钮,就提示用户要输入筛选条件

        思路分析

        对于需求一和需求二,我们首先要搞两个全屏幕弹框,然后在data中定义两个数组,一个是放常用条件的数组,另外一个是放不常用条件的数组,常用条件v-for到第一个弹框里面,不常用条件v-for到第二个弹框里面。数组里面的每一项都要配置好对应内容,比如要有筛选字段名字,比如姓名、年龄什么的。有了筛选筛选字段名字以后,还有有一个类型type,在html中我们要写三个类型的组件、比如input输入框组件,select组件,时间选择器组件。使用根据type类型通过v-show显示对应字段,比如input的type为1,select的type为2,时间选择器的type为3。是哪个type,就显示哪个组件。

        对应两个数组如下:

        topData: [ // 配置常用的筛选项
            {
             wordTitle: "姓名",
             type: 1, // 1 为input 2为select 3为DatePicker
             content: "", // content为输入框绑定的输入数据
             options: [], // options为所有的下拉框内容,可以发请求拿到存进来,这里是模拟
             optionArr: [], // optionArr为选中的下拉框内容
             timeArr: [], // timeArr为日期选择区间
            },
            {
             wordTitle: "年龄",
             type: 1,
             content: "",
             options: [],
             optionArr: [],
             timeArr: [],
            },
            {
             wordTitle: "授课班级",
             type: 2,
             content: "",
             options: [ // 发请求获取下拉框选项
              {
               id: 1,
               value: "一班",
              },
              {
               id: 2,
               value: "二班",
              },
              {
               id: 3,
               value: "三班",
              },
             ],
             optionArr: [],
             timeArr: [],
            },
            {
             wordTitle: "入职时间",
             type: 3, 
             content: "", 
             options: [], 
             optionArr: [], 
             timeArr: [], 
            },
           ],
           bottomData: [ // 配置不常用的筛选项
            {
             wordTitle: "工号",
             type: 1,
             content: "",
             options: [],
             optionArr: [],
             timeArr: [],
            },
            {
             wordTitle: "性别",
             type: 2,
             content: "",
             options: [
              {
               id: 1,
               value: "男",
              },
              {
               id: 2,
               value: "女",
              },
             ],
             optionArr: [],
             timeArr: [],
            },
           ],

        对应html代码如下:

                <div class="rightright">
                 <el-input
                  v-model.trim="item.content"
                  clearable
                  v-show="item.type == 1"
                  placeholder="请输入"
                  size="small"
                  :popper-append-to-body="false"
                 ></el-input>
                 <el-select
                  v-model="item.optionArr"
                  v-show="item.type == 2"
                  multiple
                  placeholder="请选择"
                 >
                  <el-option
                   v-for="whatItem in item.options"
                   :key="whatItem.id"
                   :label="whatItem.value"
                   :value="whatItem.id"
                   size="small"
                  >
                  </el-option>
                 </el-select>
                 <el-date-picker
                  v-model="item.timeArr"
                  v-show="item.type == 3"
                  type="daterange"
                  range-separator="至"
                  start-placeholder="开始日期"
                  end-placeholder="结束日期"
                  format="yyyy-MM-dd"
                  value-format="yyyy-MM-dd"
                 >
                 </el-date-picker>
                </div>
        

        完整代码在最后,大家先顺着思路看哦

        对于需求三需求四,可描述为,删除上面的掉到下面。点击下面的蹦到上面。所以对应操作就是把上面数组某一项追加到下面数组,然后把上面数组的这一项删掉;把下面数组的某一项追加到上面数组,然后把这一行删掉。(注意还有一个索引)对应代码如下:

        /* 点击某一项的删除小图标,把这一项添加到bottomData数组中
            然后把这一项从topData数组中删除掉(根据索引判别是哪一项) 
            最后删除一个就把索引置为初始索引 -1  */
          clickIcon(i) {
           this.bottomData.push(this.topData[i]);
           this.topData.splice(i, 1);
           this.whichIndex = -1;
          },
          // 点击底部的项的时候,通过事件对象,看看点击的是底部的哪一项
          // 然后把对应的那一项追加到topData中用于展示,同时把bottom数组
          // 中的哪一项进行删除
          clickBottomItem(event) {
           this.bottomData.forEach((item, index) => {
            if (item.wordTitle == event.target.innerText) {
             this.topData.push(item);
             this.bottomData.splice(index, 1);
            }
           });
          },
        

        对于需求五需求六就简单了,对应代码如下,完整代码注释中已经写好了

        完整代码

        <template>
         <div id="app">
          <div class="filterBtn">
           <el-button type="primary" size="small" @click="filterMaskOne = true">
            数据筛选<i class="el-icon-s-operation el-icon--right"></i>
           </el-button>
           <transition name="fade">
            <div
             class="filterMaskOne"
             v-show="filterMaskOne"
             @click="filterMaskOne = false"
            >
             <div class="filterMaskOneContent" @click.stop>
              <div class="filterHeader">
               <span>数据筛选</span>
              </div>
              <div class="filterBody">
               <div class="outPrompt" v-show="topData.length == 0">
                暂无筛选条件,请添加筛选条件...
               </div>
               <div
                class="filterBodyCondition"
                v-for="(item, index) in topData"
                :key="index"
               >
                <div
                 class="leftleft"
                 @mouseenter="mouseEnterItem(index)"
                 @mouseleave="mouseLeaveItem(index)"
                >
                 <span
                  >{{ item.wordTitle }}:
                  <i
                   class="el-icon-error"
                   v-show="whichIndex == index"
                   @click="clickIcon(index)"
                  ></i>
                 </span>
                </div>
                <div class="rightright">
                 <el-input
                  v-model.trim="item.content"
                  clearable
                  v-show="item.type == 1"
                  placeholder="请输入"
                  size="small"
                  :popper-append-to-body="false"
                 ></el-input>
                 <el-select
                  v-model="item.optionArr"
                  v-show="item.type == 2"
                  multiple
                  placeholder="请选择"
                 >
                  <el-option
                   v-for="whatItem in item.options"
                   :key="whatItem.id"
                   :label="whatItem.value"
                   :value="whatItem.id"
                   size="small"
                  >
                  </el-option>
                 </el-select>
                 <el-date-picker
                  v-model="item.timeArr"
                  v-show="item.type == 3"
                  type="daterange"
                  range-separator="至"
                  start-placeholder="开始日期"
                  end-placeholder="结束日期"
                  format="yyyy-MM-dd"
                  value-format="yyyy-MM-dd"
                 >
                 </el-date-picker>
                </div>
               </div>
              </div>
              <div class="filterFooter">
               <div class="filterBtn">
                <el-button
                 type="text"
                 icon="el-icon-circle-plus-outline"
                 @click="filterMaskTwo = true"
                 >添加筛选条件</el-button
                >
                <transition name="fade">
                 <div
                  class="filterMaskTwo"
                  v-show="filterMaskTwo"
                  @click="filterMaskTwo = false"
                 >
                  <div class="filterMaskContentTwo" @click.stop>
                   <div class="innerPrompt" v-show="bottomData.length == 0">
                    暂无内容...
                   </div>
                   <div
                    class="contentTwoItem"
                    @click="clickBottomItem"
                    v-for="(item, index) in bottomData"
                    :key="index"
                   >
                    <div class="mingzi">
                     {{ item.wordTitle }}
                    </div>
                   </div>
                  </div>
                 </div>
                </transition>
               </div>
               <div class="resetAndConfirmBtns">
                <el-button size="small" @click="resetFilter">重置</el-button>
                <el-button type="primary" size="small" @click="confirmFilter"
                 >确认</el-button
                >
               </div>
              </div>
             </div>
            </div>
           </transition>
          </div>
         </div>
        </template>
        
        <script>
        export default {
         name: "app",
         data() {
          return {
           filterMaskOne: false, // 分别用于控制两个弹框的显示与隐藏
           filterMaskTwo: false,
           whichIndex: -1, // 用于记录点击的索引
           apiFilterArr:[], //存储用户填写的筛选内容
           topData: [ // 配置常用的筛选项
            {
             wordTitle: "姓名",
             type: 1, // 1 为input 2为select 3为DatePicker
             content: "", // content为输入框绑定的输入数据
             options: [], // options为所有的下拉框内容
             optionArr: [], // optionArr为选中的下拉框内容
             timeArr: [], // timeArr为日期选择区间
            },
            {
             wordTitle: "年龄",
             type: 1,
             content: "",
             options: [],
             optionArr: [],
             timeArr: [],
            },
            {
             wordTitle: "授课班级",
             type: 2,
             content: "",
             options: [ // 发请求获取下拉框选项
              {
               id: 1,
               value: "一班",
              },
              {
               id: 2,
               value: "二班",
              },
              {
               id: 3,
               value: "三班",
              },
             ],
             optionArr: [],
             timeArr: [],
            },
            {
             wordTitle: "入职时间",
             type: 3, 
             content: "", 
             options: [], 
             optionArr: [], 
             timeArr: [], 
            },
           ],
           bottomData: [ // 配置不常用的筛选项
            {
             wordTitle: "工号",
             type: 1,
             content: "",
             options: [],
             optionArr: [],
             timeArr: [],
            },
            {
             wordTitle: "性别",
             type: 2,
             content: "",
             options: [
              {
               id: 1,
               value: "男",
              },
              {
               id: 2,
               value: "女",
              },
             ],
             optionArr: [],
             timeArr: [],
            },
           ],
          };
         },
         mounted() {
          // 在初始化加载的时候,我们就把我们配置的常用和不常用的筛选项保存一份
          // 当用户点击重置按钮的时候,再取出来使其恢复到最初的筛选条件状态
          sessionStorage.setItem("topData",JSON.stringify(this.topData))
          sessionStorage.setItem("bottomData",JSON.stringify(this.bottomData))
         },
         methods: {
          //鼠标移入显示删除小图标
          mouseEnterItem(index) {
           this.whichIndex = index;
          },
          // 鼠标离开将索引回复到默认-1
          mouseLeaveItem() {
           this.whichIndex = -1;
          },
          /* 点击某一项的删除小图标,把这一项添加到bottomData数组中
            然后把这一项从topData数组中删除掉(根据索引判别是哪一项) 
            最后删除一个就把索引置为初始索引 -1  */
          clickIcon(i) {
           this.bottomData.push(this.topData[i]);
           this.topData.splice(i, 1);
           this.whichIndex = -1;
          },
          // 点击底部的项的时候,通过事件对象,看看点击的是底部的哪一项
          // 然后把对应的那一项追加到topData中用于展示,同时把bottom数组
          // 中的哪一项进行删除
          clickBottomItem(event) {
           this.bottomData.forEach((item, index) => {
            if (item.wordTitle == event.target.innerText) {
             this.topData.push(item);
             this.bottomData.splice(index, 1);
            }
           });
          },
          // 点击确认筛选
          async confirmFilter() {
           // 如果所有的输入框的content内容为空,且选中的下拉框数组为空,且时间选择器选中的数组为空
           // 就说明用户没有输入内容,那么我们就提示用户要输入内容以后再进行筛选
           let isEmpty = this.topData.every((item)=>{
            return (item.content == "") && (item.optionArr.length == 0) && (item.timeArr.length == 0)
           })
           if(isEmpty == true){
             this.$alert('请输入内容以后再进行筛选', '筛选提示', {
             confirmButtonText: '确定'
            });
           }else{
            // 收集参数发筛选请求,这里要分类型,把不为空的既有用户输入内容的
            // 存到存到数据筛选的数组中去,然后发请求给后端。
            this.topData.forEach((item)=>{
             if(item.type == 1){
              if(item.content != ""){
               let filterItem = {
                field:item.wordTitle,
                value:item.content
               }
               this.apiFilterArr.push(filterItem)
              }
             }else if(item.type == 2){
              if(item.optionArr.length > 0){
               let filterItem = {
                field:item.wordTitle,
                value:item.optionArr
               }
               this.apiFilterArr.push(filterItem)
              }
             }else if(item.type == 3){
              if(item.timeArr.length > 0){
               let filterItem = {
                field:item.wordTitle,
                value:item.timeArr
               }
               this.apiFilterArr.push(filterItem)
              }
             } 
            })
            // 把筛选的内容放到一个数组里面,传递给后端(当然不一定把参数放到数组里面)
            // 具体以怎样的形式传递给后端,可以具体商量
            console.log("带着筛选内容发请求",this.apiFilterArr);
           }
          },
          // 重置时,再把最初的配置筛选项取出来赋给对应的两个数组
          resetFilter() {
           this.topData = JSON.parse(sessionStorage.getItem("topData"))
           this.bottomData = JSON.parse(sessionStorage.getItem("bottomData"))
          },
         },
        };
        </script>
        <style lang="less" scoped>
        .filterBtn {
         width: 114px;
         height: 40px;
         .filterMaskOne {
          top: 0;
          left: 0;
          position: fixed;
          width: 100%;
          height: 100%;
          z-index: 999;
          background-color: rgba(0, 0, 0, 0.3);
          .filterMaskOneContent {
           position: absolute;
           top: 152px;
           right: 38px;
           width: 344px;
           height: 371px;
           background-color: #fff;
           box-shadow: 0px 0px 4px 3px rgba(194, 194, 194, 0.25);
           border-radius: 4px;
           .filterHeader {
            width: 344px;
            height: 48px;
            border-bottom: 1px solid #e9e9e9;
            span {
             display: inline-block;
             font-weight: 600;
             font-size: 16px;
             margin-left: 24px;
             margin-top: 16px;
            }
           }
           .filterBody {
            width: 344px;
            height: 275px;
            overflow-y: auto;
            overflow-x: hidden;
            box-sizing: border-box;
            padding: 12px 24px 0 24px;
            .outPrompt {
             color: #666;
            }
            .filterBodyCondition {
             width: 100%;
             min-height: 40px;
             display: flex;
             margin-bottom: 14px;
             .leftleft {
              width: 88px;
              height: 40px;
              display: flex;
              align-items: center;
              margin-right: 20px;
              span {
               position: relative;
               font-size: 14px;
               color: #333;
               i {
                color: #666;
                right: -8px;
                top: -8px;
                position: absolute;
                font-size: 15px;
                cursor: pointer;
               }
               i:hover {
                color: #5f95f7;
               }
              }
             }
             .rightright {
              width: calc(100% - 70px);
              height: 100%;
              /deep/ input::placeholder {
               color: rgba(0, 0, 0, 0.25);
               font-size: 13px;
              }
              /deep/ .el-input__inner {
               height: 40px;
               line-height: 40px;
              }
              /deep/ .el-select {
               .el-input--suffix {
                /deep/ input::placeholder {
                 color: rgba(0, 0, 0, 0.25);
                 font-size: 13px;
                }
                .el-input__inner {
                 border: none;
                }
                .el-input__inner:hover {
                 background: rgba(95, 149, 247, 0.05);
                }
               }
              }
              .el-date-editor {
               width: 100%;
               font-size: 12px;
              }
              .el-range-editor.el-input__inner {
               padding-left: 2px;
               padding-right: 0;
              }
              /deep/.el-range-input {
               font-size: 13px !important;
              }
              /deep/ .el-range-separator {
               padding: 0 !important;
               font-size: 12px !important;
               width: 8% !important;
               margin: 0;
              }
              /deep/ .el-range__close-icon {
               width: 16px;
              }
             }
            }
           }
           .filterFooter {
            width: 344px;
            height: 48px;
            display: flex;
            justify-content: space-between;
            align-items: center;
            box-sizing: border-box;
            padding-left: 24px;
            padding-right: 12px;
            border-top: 1px solid #e9e9e9;
            .filterBtn {
             .filterMaskTwo {
              position: fixed;
              top: 0;
              left: 0;
              width: 100%;
              height: 100%;
              background-color: rgba(0, 0, 0, 0.3);
              z-index: 1000;
              .filterMaskContentTwo {
               width: 240px;
               height: 320px;
               background: #ffffff;
               box-shadow: 0px 0px 4px 3px rgba(194, 194, 194, 0.25);
               border-radius: 4px;
               position: absolute;
               top: 360px;
               right: 180px;
               overflow-y: auto;
               box-sizing: border-box;
               padding: 12px 0 18px 0;
               overflow-x: hidden;
               .innerPrompt {
                color: #666;
                width: 100%;
                padding-left: 20px;
                margin-top: 12px;
               }
               .contentTwoItem {
                width: 100%;
                height: 36px;
                line-height: 36px;
                font-size: 14px;
                color: #333333;
                cursor: pointer;
                .mingzi {
                 width: 100%;
                 height: 36px;
                 box-sizing: border-box;
                 padding-left: 18px;
                }
               }
               .contentTwoItem:hover {
                background: rgba(95, 149, 247, 0.05);
               }
              }
             }
            }
           }
          }
         }
        }
        // 控制淡入淡出效果
        .fade-enter-active,
        .fade-leave-active {
         transition: opacity 0.3s;
        }
        .fade-enter,
        .fade-leave-to {
         opacity: 0;
        }
        </style>
        
        

        总结

        这里面需要注意的就是鼠标移入移出显示对应的删除小图标。思路大致就这样,敲代码不易,咱们共同努力。

        以上就是vue 使用饿了么UI仿写teambition的筛选功能的详细内容,更多关于vue 仿写teambition的筛选功能的资料请关注NICE源码其它相关文章!

        免责声明:
        1、本网站所有发布的源码、软件和资料均为收集各大资源网站整理而来;仅限用于学习和研究目的,您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。 不得使用于非法商业用途,不得违反国家法律。否则后果自负!

        2、本站信息来自网络,版权争议与本站无关。一切关于该资源商业行为与www.niceym.com无关。
        如果您喜欢该程序,请支持正版源码、软件,购买注册,得到更好的正版服务。
        如有侵犯你版权的,请邮件与我们联系处理(邮箱:skknet@qq.com),本站将立即改正。

        NICE源码网 JavaScript vue 使用饿了么UI仿写teambition的筛选功能 https://www.niceym.com/31801.html