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 18 Vue.js
vue+elementUI动态增加表单项并添加验证的代码详解
Dec 17 Vue.js
Vue与React的区别和优势对比
Dec 18 Vue.js
vue 通过base64实现图片下载功能
Dec 19 Vue.js
Vue组件简易模拟实现购物车
Dec 21 Vue.js
浅析vue中的nextTick
Dec 28 Vue.js
Vue包大小优化的实现(从1.72M到94K)
Feb 18 Vue.js
如何理解Vue简单状态管理之store模式
May 15 Vue.js
一定要知道的 25 个 Vue 技巧
Nov 02 Vue.js
Vue的列表之渲染,排序,过滤详解
Feb 24 Vue.js
Vue+TypeScript中处理computed方式
Apr 02 Vue.js
vue-cil之axios的二次封装与proxy反向代理使用说明
Apr 07 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
4.与数据库的连接
2006/10/09 PHP
mysql建立外键
2006/11/25 PHP
php5编程中的异常处理详细方法介绍
2008/07/29 PHP
php Xdebug 调试扩展的安装与使用.
2010/03/13 PHP
46 个非常有用的 PHP 代码片段
2016/02/16 PHP
PHP基于PDO调用sqlserver存储过程通用方法【基于Yii框架】
2017/10/07 PHP
PHP设计模式之策略模式原理与用法实例分析
2019/04/04 PHP
jquery购物车实时结算特效实现思路
2013/09/23 Javascript
window.location不跳转的问题解决方法
2014/04/17 Javascript
详解AngularJS中的表单验证(推荐)
2016/11/17 Javascript
JavaScript基于replace+正则实现ES6的字符串模版功能
2017/04/25 Javascript
微信小程序 navbar实例详解
2017/05/11 Javascript
vue 设置proxyTable参数进行代理跨域
2018/04/09 Javascript
解决vue初始化项目时,一直卡在Project description上的问题
2019/10/31 Javascript
微信小程序手动添加收货地址省市区联动
2020/05/18 Javascript
Python isinstance函数介绍
2015/04/14 Python
Python基于Flask框架配置依赖包信息的项目迁移部署
2018/03/02 Python
python实现写数字文件名的递增保存文件方法
2018/10/25 Python
Python3直接爬取图片URL并保存示例
2019/12/18 Python
Python基于numpy模块实现回归预测
2020/05/14 Python
Pycharm插件(Grep Console)自定义规则输出颜色日志的方法
2020/05/27 Python
python获取linux系统信息的三种方法
2020/10/14 Python
python-jwt用户认证食用教学的实现方法
2021/01/19 Python
如何用Python进行时间序列分解和预测
2021/03/01 Python
JackJones官方旗舰店:杰克琼斯男装
2018/03/27 全球购物
Footshop罗马尼亚:最好的运动鞋选择
2019/09/10 全球购物
意大利时尚精品店:Nugnes 1920
2020/02/10 全球购物
PHP中如何创建和修改数组
2012/05/02 面试题
行政管理专业推荐信
2013/11/02 职场文书
广告创意求职信
2014/03/17 职场文书
机电专业毕业生求职信
2014/07/01 职场文书
党员个人对照检查材料范文
2014/09/24 职场文书
上级领导检查欢迎词
2015/09/30 职场文书
公司转让协议书
2016/03/19 职场文书
nginx location中多个if里面proxy_pass的方法
2021/03/31 Servers
浅谈redis五大数据结构和使用场景
2021/04/12 Redis