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

作者:水冗水孚 时间:2024-04-27 16:05:09 

目录
  • 问题描述

    • 大致的功能效果有如下

  • 思路分析

    • 完整代码

      • 总结

        问题描述

        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>

        总结

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

        来源:https://segmentfault.com/a/1190000039298567

        标签:vue,饿了么UI,teambition,筛选功能
        0
        投稿

        猜你喜欢

      • python基于隐马尔可夫模型实现中文拼音输入

        2023-05-08 21:58:00
      • 使用JavaScript构建JSON格式字符串实现步骤

        2024-04-18 09:42:12
      • Python中优雅使用assert断言的方法实例

        2021-03-27 12:56:58
      • Asp实现伪静态的方法

        2007-09-29 21:27:00
      • python爬虫URL重试机制的实现方法(python2.7以及python3.5)

        2023-08-06 07:51:43
      • python如何发布自已pip项目的方法步骤

        2023-01-22 01:17:36
      • Python数据可视化实现漏斗图过程图解

        2022-07-29 19:02:43
      • python使用xmlrpclib模块实现对百度google的ping功能

        2023-05-06 19:02:21
      • Python图像运算之图像掩膜直方图和HS直方图详解

        2023-03-01 03:01:45
      • Python 类的继承实例详解

        2021-04-30 15:54:09
      • JavaScript解决Joseph问题

        2008-06-21 17:11:00
      • MySQL一个索引最多有多少个列?真实的测试例子

        2024-01-20 18:32:39
      • oracle 删除重复数据

        2009-07-23 14:46:00
      • 详解python代码模块化

        2023-08-08 23:08:23
      • CSS中写expression可能会在Chrome中有问题

        2010-01-29 13:10:00
      • 如何创建 Firefox 的 Jetpack 扩展

        2009-10-13 20:55:00
      • 基于Python手写拼音识别

        2022-10-22 09:24:09
      • 详解django+django-celery+celery的整合实战

        2022-11-14 12:25:13
      • Python使用UDP实现720p视频传输的操作

        2023-12-04 09:32:49
      • Django修改端口号与地址的三种方式

        2023-06-22 00:48:27
      • asp之家 网络编程 m.aspxhome.com