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


Posted in Vue.js onMarch 01, 2021

问题描述

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的筛选功能的资料请关注三水点靠木其它相关文章!

Vue.js 相关文章推荐
解决Vue-cli3没有vue.config.js文件夹及配置vue项目域名的问题
Dec 04 Vue.js
vue实现简易的双向数据绑定
Dec 29 Vue.js
Vue项目中使用mock.js的完整步骤
Jan 12 Vue.js
vue使用过滤器格式化日期
Jan 20 Vue.js
vue穿梭框实现上下移动
Jan 29 Vue.js
Vue项目打包部署到apache服务器的方法步骤
Feb 01 Vue.js
vue脚手架项目创建步骤详解
Mar 02 Vue.js
vue使用v-model进行跨组件绑定的基本实现方法
Apr 28 Vue.js
一文带你理解vue创建一个后台管理系统流程(Vue+Element)
May 18 Vue.js
vue+spring boot实现校验码功能
May 27 Vue.js
Vue.js中v-bind指令的用法介绍
Mar 13 Vue.js
vue实现input输入模糊查询的三种方式
Aug 14 Vue.js
vue实现拖拽进度条
Mar 01 #Vue.js
vue 使用 v-model 双向绑定父子组件的值遇见的问题及解决方案
Mar 01 #Vue.js
vue前端和Django后端如何查询一定时间段内的数据
Feb 28 #Vue.js
vue-router路由懒加载及实现的3种方式
Feb 28 #Vue.js
vue-router懒加载的3种方式汇总
Feb 28 #Vue.js
Vue SPA 首屏优化方案
Feb 26 #Vue.js
vue 动态添加的路由页面刷新时失效的原因及解决方案
Feb 26 #Vue.js
You might like
比较时间段一与时间段二是否有交集的php函数
2011/05/31 PHP
php array_pop()数组函数将数组最后一个单元弹出(出栈)
2011/07/12 PHP
thinkPHP5.0框架事务处理操作简单示例
2018/09/07 PHP
IE无法设置短域名下Cookie
2010/09/23 Javascript
jQuery ready函数滥用分析
2011/02/16 Javascript
javascript学习笔记(三)显示当时时间的代码
2011/04/08 Javascript
推荐30个新鲜出炉的精美 jQuery 效果
2012/03/26 Javascript
把input初始值不写value的具体实现方法
2013/07/04 Javascript
javascript数组操作方法小结和3个属性详细介绍
2014/07/05 Javascript
推荐一个自己用的封装好的javascript插件
2015/01/29 Javascript
Bootstrap每天必学之警告框插件
2016/04/26 Javascript
微信小程序 生命周期和页面的生命周期详细介绍
2017/01/19 Javascript
Javascript中toFixed计算错误(依赖银行家舍入法的缺陷)解决方法
2017/08/22 Javascript
解决Mac安装thrift因bison报错的问题
2018/05/17 Javascript
element-ui循环显示radio控件信息的方法
2018/08/24 Javascript
vue中的watch监听数据变化及watch中各属性的详解
2018/09/11 Javascript
Vue弹出菜单功能的实现代码
2018/09/12 Javascript
JS数组去重的6种方法完整实例
2018/12/08 Javascript
vue移动端实现手机左右滑动入场动画
2020/06/17 Javascript
Python实现的石头剪子布代码分享
2014/08/22 Python
python实用代码片段收集贴
2015/06/03 Python
python编写分类决策树的代码
2017/12/21 Python
Python基于OpenCV实现视频的人脸检测
2018/01/23 Python
Python通用函数实现数组计算的方法
2019/06/13 Python
对python tkinter窗口弹出置顶的方法详解
2019/06/14 Python
python如何停止递归
2020/09/09 Python
美国百货齐全的精品网站,提供美式风格的产品:Overstock.com
2016/07/22 全球购物
2019年c语言经典面试题目
2016/08/17 面试题
爱国卫生月活动总结范文
2014/04/25 职场文书
中国梦演讲稿5分钟
2014/08/19 职场文书
国家机关领导干部民主生活会对照检查材料思想汇报
2014/09/17 职场文书
党委工作总结2015
2015/04/27 职场文书
中学教师师德师风承诺书
2015/04/28 职场文书
2015年机关纠风工作总结
2015/05/15 职场文书
员工保密协议范本,您一定得收藏!很有用!
2019/08/08 职场文书
vue+echarts实现多条折线图
2022/03/21 Vue.js