好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

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

问题描述

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的筛选功能的资料请关注服务器之家其它相关文章!

原文链接:https://segmentfault测试数据/a/1190000039298567

查看更多关于vue 使用饿了么UI仿写teambition的筛选功能的详细内容...

  阅读:42次