vue 使用饿了么UI仿写teambition的筛选功能
作者:水冗水孚 发布时间:2024-04-27 16:05:09
目录
问题描述
大致的功能效果有如下
思路分析
完整代码
总结
问题描述
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
![](https://www.aspxhome.com/images/zang.png)
![](https://www.aspxhome.com/images/jiucuo.png)
猜你喜欢
- 可以利用in运算符来进行判断,如果在指定的序列中找到值返回True,否则返回False。运算符not in表示如果在指定的序列中没有找到值返
- 1、django应用Celerydjango框架请求/响应的过程是同步的,框架本身无法实现异步响应。但是我们在项目过程中会经常会遇到一些耗时
- 目标:利用python读取dicom文件,并进行处理生成info.txt和raw文件实现:通过pydicom读取dicom文件代码:impo
- 函数介绍Socket对象方法:服务端:函数描述.bind()绑定地址关键字,AF_INET下以元组的形式表示地址。常用bind((host,
- 之前学习深度学习算法,都是使用网上现成的数据集,而且都有相应的代码。到了自己开始写论文做实验,用到自己的图像数据集的时候,才发现无从下手 ,
- 本文实例讲述了微信小程序使用slider设置数据值及switch开关组件功能。分享给大家供大家参考,具体如下:1、效果展示2、关键代码① i
- 方法一:简单,得不到参数,只有一个虚拟路径 代码如下:GetUrl =request("url") 例如:http://
- 主键的生成方式主要有三种: 一. 数据库自动生成 二. GUID 三. 开发创建 严格讲这三种产生方式有一定的交叉点,其定位方式将在下面进行
- 一、numpy是什么?扩展库numpy是Python支持科学计算的重要扩展库,是数据分析和科学计算领域如scipy、pandas、sklea
- php中可以把函数名通过字符串的方式传递给一个变量,然后通过此变量动态调用函数下面是一个简单的动态函数调用范例<html><
- dim dr dr="2123123" dr1=Cint(dr) dr2=Clng(dr) 可参考如下函数说明: CIn
- 一、this指向构造函数实例化对象在上篇文章中,我们提到了使用new和不使用new调用构造函数的区别,如下例:function Benjam
- python函数一、函数定义函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。函数能提高应用的模块性,和代码的重复利用率。你
- def getFibonacci(num): res=[0,1] a=0 b=1 for x in
- Python实现完整邮件先上效果:一、邮箱端设置首先,要对邮件进行一下设置,在邮箱端获取一个授权码。1、首先登录网页版126邮箱
- 目录何时使用 Menu 组件?用法参数方法add_cascade(**options)add_checkbutton(**options)a
- 这篇文章主要介绍了Python csv文件的读写操作实例详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需
- operator模块是python中内置的操作符函数接口,它定义了一些算术和比较内置操作的函数。operator模块是用c实现的,所以执行速
- 一、模拟登录图书馆管理系统我们可以先看一下登录页面(很多学校这些管理系统页面就是很low):两种方式去模拟登录图书馆:1. 构造登录表单进行
- 代码如下:<% class MyClass Dim var '公共变量必须使