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项目实现主题切换的多种方法
Nov 26 Vue.js
vue祖孙组件之间的数据传递案例
Dec 07 Vue.js
Vue实现指令式动态追加小球动画组件的步骤
Dec 18 Vue.js
vue 使用rules对表单字段进行校验的步骤
Dec 25 Vue.js
vue 根据选择的月份动态展示日期对应的星期几
Feb 06 Vue.js
vue如何使用rem适配
Feb 06 Vue.js
vue实现桌面向网页拖动文件的示例代码(可显示图片/音频/视频)
Mar 01 Vue.js
vue实现锚点定位功能
Jun 29 Vue.js
关于Vue中的options选项
Mar 22 Vue.js
vue-cli3.0修改打包后的文件名和文件地址,打包后本地运行报错解决
Apr 06 Vue.js
Vue Mint UI mt-swipe的使用方式
Jun 05 Vue.js
vue3 自定义图片放大器效果的示例代码
Jul 23 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防注入安全代码
2008/04/09 PHP
php中json_encode中文编码问题分析
2011/09/13 PHP
php+js实现图片的上传、裁剪、预览、提交示例
2013/08/27 PHP
php与flash as3 socket通信传送文件实现代码
2014/08/16 PHP
PHP实现打包zip并下载功能
2018/06/12 PHP
详解laravel安装使用Passport(Api认证)
2018/07/27 PHP
php使用event扩展的io复用测试的示例
2020/10/20 PHP
JavaScript 解析Json字符串的性能比较分析代码
2009/12/16 Javascript
Wordpress ThickBox 点击图片显示下一张图的修改方法
2010/12/11 Javascript
window.open关于浏览器拦截问题分析及解决方法
2013/02/05 Javascript
原生javascript实现拖动元素示例代码
2014/09/01 Javascript
javascript数字验证的实例代码(推荐)
2016/08/20 Javascript
JavaScript 中 avalon绑定属性总结
2016/10/19 Javascript
利用canvas实现的加载动画效果实例代码
2017/07/05 Javascript
基于Datatables跳转到指定页的简单实例
2017/11/09 Javascript
详解React项目如何修改打包地址(编译输出文件地址)
2019/03/21 Javascript
微信小程序实现传递多个参数与事件处理
2019/08/12 Javascript
[03:00]2018完美盛典_最佳英雄奖
2018/12/17 DOTA
[01:15]PWL S2开团时刻第二期——他们杀 我就白给
2020/11/25 DOTA
[07:25]DOTA2-DPC中国联赛2月5日Recap集锦
2021/03/11 DOTA
详解Python的collections模块中的deque双端队列结构
2016/07/07 Python
你应该知道的python列表去重方法
2017/01/17 Python
Python 装饰器深入理解
2017/03/16 Python
Python实现按学生年龄排序的实际问题详解
2017/08/29 Python
python实现雨滴下落到地面效果
2018/06/21 Python
python 去除txt文本中的空格、数字、特定字母等方法
2018/07/24 Python
python3+PyQt5 实现Rich文本的行编辑方法
2019/06/17 Python
酒店管理专业学生求职信
2013/09/27 职场文书
绿色家庭事迹材料
2014/05/01 职场文书
小学综治宣传月活动总结
2014/07/02 职场文书
工作汇报开头与结尾怎么写
2014/11/08 职场文书
人与自然观后感
2015/06/16 职场文书
丧事答谢词大全
2015/09/30 职场文书
python数据可视化JupyterLab实用扩展程序Mito
2021/11/20 Python
Nginx下SSL证书安装部署步骤介绍
2021/12/06 Servers
Beekeeper Studio开源数据库管理工具比Navicat更炫酷
2022/06/21 数据库