1.创建组件
components/FilterBar/FilterBar.vue
<template>
<div class="filterbar" :style="{'top': top + 'px'}">
<div class="container">
<div class="row">
<div
class="col"
:class="{'selected': index == selectedIndexMenu}"
@click="handleShowDialog(barMenu, index)"
v-for="(barMenu, index) in barMenus"
:key="index"
>
{{barMenu.name}}<span :class="index == selectedIndexMenu ? barMenu.selectIcon : barMenu.defaultIcon"></span>
</div>
</div>
<filter-bar-pop
:filterTop="top"
:show-dialog="isShow"
:hasTabHeader="hasTabHeader"
:menu="selectedMenu"
@changeTab="handleChangeTab"
@changeMainItem="handleChangeMainItem"
@changeSelect="changeSelect"
@closeDialog="handleCloseDialog"
></filter-bar-pop>
</div>
</div>
</template> <script>
import FilterBarPop from './FilterBarPop'
export default {
props: {
barMenus: {
type: Array,
required: true,
validator: function (value) {
//TODO:验证数据有效性
return true;
}
},
top: String
},
data() {
return {
isShow: false,
hasTabHeader: false,
selectedMenu: {},
selectedIndexMenu: undefined
}
},
methods: {
handleShowDialog(menu, index) {
this.isShow = true;
this.selectedMenu = menu;
this.selectedIndexMenu = index;
if (menu.showTabHeader) {
this.hasTabHeader = true;
} else {
this.hasTabHeader = false;
}
let _menu = JSON.parse(JSON.stringify(menu));
_menu.tabs = {};
this.$emit('showDialog', _menu);
},
handleChangeTab(tab) {
this.$emit('changeTab', tab.index);
},
handleChangeMainItem(mainItem) {
let _mainItem = JSON.parse(JSON.stringify(mainItem));
this.$emit('changeMainItem', _mainItem);
},
handleCloseDialog() {
this.isShow = false;
this.selectedIndexMenu = -1;
this.$emit('closeDialog');
},
changeSelect() {
var selectData = [];
this.barMenus.forEach(function (barMenu, index, arr) {
let _selectBarData = {};
// console.log("barMenu.name: " + barMenu.name);
_selectBarData.name = barMenu.name;
_selectBarData.value = barMenu.value;
_selectBarData.tab = {};
let tab = barMenu.tabs[barMenu.selectIndex];
// console.log("tab.name: " + tab.name);
_selectBarData.tab.name = tab.name;
_selectBarData.tab.value = tab.value;
let mainItem = tab.detailList[tab.selectIndex];
_selectBarData.tab.mainItem = {}
// console.log("mainItem.name: " + mainItem.name);
_selectBarData.tab.mainItem.name = mainItem.name;
_selectBarData.tab.mainItem.value = mainItem.vaule;
let subItem = false;
if (mainItem.list) {
subItem = mainItem.list[mainItem.selectIndex];
_selectBarData.tab.mainItem.subItem = {};
// console.log("subItem.name: " + subItem.name);
_selectBarData.tab.mainItem.subItem.name = subItem.name;
_selectBarData.tab.mainItem.subItem.value = subItem.value;
} else {
_selectBarData.tab.mainItem.subItem = subItem;
}
selectData.push(_selectBarData);
});
this.$emit('changeSelect', selectData);
}
},
components: {
'filter-bar-pop': FilterBarPop
}
}
</script> <style lang="scss">
.filterbar {
width: 100%;
background: #fff;
position: fixed;
top: 0;
left: 0;
right: 0;
.container {
width: 100%;
outline: 1px solid #DBDCDE;
position: relative;
.row {
display: flex;
display: -ms-flexbox;
display: -moz-box;
display: -webkit-box;
display: -webkit-flex;
flex-direction: row;
-webkit-flex-direction: row;
justify-content: space-around;
-webkit-box-pack: space-around;
-moz-box-pack: space-around;
-ms-flex-pack: space-around;
width: 90%;
height: 40px;
margin: 0 auto;
line-height: 40px;
.selected {
color: orange;
}
.col {
span {
margin-left: 5px;
vertical-align: middle;
}
}
}
}
}
</style>
components/FilterBar/FilterBarPop.vue
<template>
<transition name="fade">
<div class="filterbarpop-wrap" v-if="visible" :style="{'top': bgTop + 'px'}">
<div class="filterbarpop-bg" @click="closeDialog" :style="{'top': bgTop + 'px'}"></div>
<div class="filterbarpop">
<div class="tab-bar" v-show="hasTabHeader">
<a href="javascript:;" :style="{'flex': column}" role="button" @click="clickTab(tab, index)" v-for="(tab, index) in menu.tabs"
:class="{'selected': selectIndexTab == index}"><span :class="tab.icon"></span>{{tab.name}}</a>
</div>
<div class="main">
<div class="main-sidebar" :class="{'full-line': !items,'bg-style':items,'line-style':!items,}">
<div v-if="menu.type !== 'filter'" class="item" @click="clickSidebar(sidemenu, index)" v-for="(sidemenu, index) in sideMenus.detailList"
:class="{'selected': currentSelectIndex == index}">
<span :class="sidemenu.icon"></span>{{ sidemenu.name }}
</div>
<div v-if="menu.type == 'filter'" v-for="(sm, _index) in menu.tabs">
<div class="filter-name">{{sm.name}}</div>
<div class="filter-item">
<span v-for="(sidemenu, index) in sm.detailList" class="item-operation" @click="clickFilterbar(sm, _index, index)" :class="{'multi-selected': sidemenu.selectIndex == index}">
{{ sidemenu.name }}
</span>
</div>
</div>
<div v-if="menu.type == 'filter'" class="filter-btns">
<a href="javascript:;" role="button" @click="handleClean">取消</a>
<a href="javascript:;" role="button" @click="handleEnsure">确认</a>
</div>
</div>
<div class="main-list line-style" v-if="items">
<span class="item" @click="clickItem(item, index)" v-for="(item, index) in items.list" :class="{'selected': currentSelectIndex == sideMenus.selectIndex && items.selectIndex == index}">{{item.name}}</span>
</div>
</div>
</div>
</div>
</transition>
</template> <script>
export default {
props: {
menu: {
type: Object
},
showDialog: {
type: Boolean,
default: true
},
hasTabHeader: {
type: Boolean,
default: true
},
filterTop: {
type: String
}
},
data() {
return {
selectIndexTab: 0,
currentSelectIndex: 0,
sideMenus: {},
items: {},
column: '',
visible: false,
top: 1,
bgTop: 0,
range: {}
}
},
mounted() {
this.bgTop = document.querySelector('.filterbar').offsetHeight + this.filterTop / 1;
},
watch: {
showDialog(v) {
this.visible = v;
if (v) {
//初始化数据
this.initData();
}
},
menu(m) {
//根据tabs数量计算列宽
this.column = '0 0 ' + 100 / m.tabs.length + '%';
//初始化数据
this.initData();
}
},
methods: {
//初始化数据
initData(tabIndex) {
var tmpTabIndx = 0;
tabIndex === undefined ? tmpTabIndx = this.menu.selectIndex : tmpTabIndx = tabIndex
//判断tabindex的范围是否在数组内
if (tmpTabIndx >= 0 && tmpTabIndx < this.menu.tabs.length) {
this.selectIndexTab = tmpTabIndx;
} else {
this.selectIndexTab = 0;
}
//确认选中tab的一级列表
this.sideMenus = this.menu.tabs[this.selectIndexTab];
//如果当前选中tab是对应选中结果的tab
// debugger;
if (this.selectIndexTab == this.menu.selectIndex) {
this.currentSelectIndex = this.sideMenus.selectIndex;
}
// else{
// this.sideMenus.selectIndex = -1;
// this.currentSelectIndex = -1;
// }
//判断是否包含二级列表,包含则赋值
//如果一级列表的选中状态正确,则查询二级列表
if (this.currentSelectIndex >= 0 && this.currentSelectIndex < this.sideMenus.detailList.length) {
//判断是否有二级列表
if (this.sideMenus.detailList[this.currentSelectIndex].list) {
this.items = this.sideMenus.detailList[this.currentSelectIndex];
} else {
//不显示二级列表
this.items = false;
}
} else { //如果一级列表选中状态不正确,按第一项的的数据判断
//判断是否有二级列表
if (this.sideMenus.detailList[0].list) {
//显示空的二级列表
this.items = [];
} else {
//不显示二级列表
this.items = false;
}
}
},
//修改选项
changeSelect(index) {
//记录tabIndex
this.menu.selectIndex = this.selectIndexTab;
//记录一级列表选项
this.sideMenus.selectIndex = this.currentSelectIndex;
if (this.items) {
//确认二级列表选项
this.items.selectIndex = index;
//显示名称
this.menu.name = this.items.list[this.items.selectIndex].name;
this.menu.value = this.items.list[this.items.selectIndex].value;
} else {
//显示名称
this.menu.name = this.sideMenus.detailList[this.sideMenus.selectIndex].name;
this.menu.value = this.sideMenus.detailList[this.sideMenus.selectIndex].value;
}
this.$emit('changeSelect');
this.closeDialog();
},
// 帅选修改选项
changeRangeSelect() {
this.menu.name = '筛选';
for(var i in this.range){
if(Object.keys(this.range[i].value).length == 0){
delete this.range[i]
}
} this.menu.value = Object.keys(this.range).length > 0 ? this.range : '';
this.$emit('changeSelect');
this.closeDialog();
},
// 选择Tab菜单
clickTab(tab, index) {
if (index !== this.selectIndexTab) {
//根据选中的tab初始化数据
this.initData(index);
this.$emit('changeTab', {
tab,
index
})
}
},
// 筛选方法
clickFilterbar(v, I, i) {
v.detailList[i].selectIndex = i;
// debugger
if(!this.range[I]){
this.range[I] = {name: v.name, value: {}};
this.range[I].value[i] = v.detailList[i].value;
} else {
if(!this.range[I].value[i]){
this.range[I].value[i] = v.detailList[i].value;
} else {
delete this.range[I].value[i];
v.detailList[i].selectIndex = -1;
}
}
},
// 点击左侧列表
clickSidebar(v, i) {
if (this.currentSelectIndex !== i) {
this.currentSelectIndex = i;
//存在二级列表
if (this.sideMenus.detailList[this.currentSelectIndex].list) {
this.items = this.sideMenus.detailList[this.currentSelectIndex];
} else {
//只有一级列表,记录选项,退出
this.changeSelect();
}
this.$emit('changeMainItem', {
v,
i
});
}
},
// 点击右侧列表
clickItem(v, i) {
//只有一级列表,记录选项,退出
this.changeSelect(i);
},
// 关闭弹框
closeDialog() {
this.visible = false;
this.$emit('closeDialog');
},
// 提交已选内容
handleEnsure() {
this.changeRangeSelect();
this.$emit('changeMainItem', this.range);
// this.closeDialog();
},
// 清除已选内容
handleClean() {
this.menu.tabs.map(item => {
item.detailList.map(_item => {
_item.selectIndex = -1;
})
});
this.range = {};
}
}
}
/**
TODOS:
1. 需要一个属性去辨别帅选项
2. 多选
3. 添加多选框 */
</script> <style lang="scss">
.fade-enter-active,
.fade-leave-active {
transition: opacity .5s
}
.fade-enter,
.fade-leave-active {
opacity: 0
}
.filterbarpop-wrap {
position: fixed;
width: 100%;
top: 0;
bottom: 0;
left: 0;
overflow: hidden;
max-height: 100%;
.filterbarpop-bg {
position: fixed;
top: 0;
bottom: 0;
left: 0;
width: 100%;
background: rgba(0, 0, 0, .6);
}
.filterbarpop {
position: absolute;
width: 100%;
border-top: 1px solid #ccc;
.tab-bar {
width: 100%;
display: flex;
display: -ms-flexbox;
display: -moz-box;
display: -webkit-box;
display: -webkit-flex;
flex-directives: row;
-webkit-flex-direction: row;
align-items: center;
-webkit-align-items: center;
-webkit-box-align: center;
-moz-box-align: center;
-ms-flex-align: center;
height: 40px;
.selected {
border-bottom: 2px solid orange;
box-sizing: border-box;
}
a {
background: #fff;
height: 100%;
line-height: 40px;
text-decoration: none;
color: #323232;
text-align: center;
}
}
.main {
display: flex;
display: -webkit-flex;
flex-direction: row;
-webkit-flex-direction: row;
height: 250px;
background: #fff;
.main-sidebar {
flex: 0 0 50%;
overflow: auto;
width: 100%;
}
.full-line {
flex: 0 0 100%;
div {
text-align: left; // text-indent: 1.5em;
}
}
.item-operation {
display: inline-block;
padding: 10px 4px 10px 4px;
border: 1px solid rgb(91, 149, 255);
border-radius: 3px;
height: 0;
line-height: 1px;
}
.multi-selected {
background: rgb(91, 149, 255);
color: #fff !important;
}
.filter-item {
border-top: 1px solid #ccc;
border-bottom: 1px solid #ccc;
padding: 13px 0 5px 10px;
span {
margin-right: 8px;
margin-bottom: 8px;
}
}
.filter-name {
padding: 10px 0 10px 10px;
}
.filter-btns {
display: flex;
display: -webkit-flex;
flex-direction: row;
-webkit-flex-direction: row;
justify-content: space-around;
-webkit-box-pack: space-around;
-moz-box-pack: space-around;
-ms-flex-pack: space-around;
position: absolute;
bottom: -40px;
width: 100%;
line-height: 40px;
z-index: 100;
background: #fff;
a {
display: block;
width: 100%;
text-align: center;
text-decoration: none;
color: #ccc;
border-top: 1px solid #ccc;
&:last-child {
background: #39f;
color: #fff;
}
}
}
.main-list {
flex: 0 0 50%;
overflow: auto;
span:active {
background: #f5f5f5;
}
}
.line-style {
.item {
text-align: left;
margin-left: 10px;
padding-left: 15px;
border-bottom: 1px solid #ccc;
position: relative;
&.selected {
color: orange;
border-color: orange;
span {
color: orange;
}
}
.checkbox {
position: absolute;
right: 50px;
top: 10px;
}
}
}
.bg-style {
.item {
background-color: #f5f5f5;
&.selected {
background-color: #FFF;
}
}
}
.item {
display: inline-block;
height: 40px;
background: #fff;
line-height: 40px;
width: 100%;
text-decoration: none;
color: #444;
span {
font-size: 14px;
color: #888;
margin-right: 10px;
vertical-align: middle;
}
&:active {
color: #fff;
}
}
}
}
}
</style>
2.页面调用
pages/FilterBarTest
<!-- 移动端筛选条件 测试页 -->
<template>
<div>
<!-- 标题栏 -->
<x-header title="移动端筛选条件 测试页"></x-header>
<!-- 内容部分 -->
<FilterBar
top="40"
:barMenus="barMenus"
@showDialog="handleShowDialog"
@closeDialog="handleCloseDialog"
@changeTab="handleChangeTab"
@changeMainItem="handleChangeMainItem"
@changeSelect="changeData">
</FilterBar>
</div>
</template> <script>
import { XHeader } from 'vux'
// 引入组件
import FilterBar from '../../components/FilterBar/FilterBar.vue'
// 引入假数据
import barMenus from './data.js'; export default {
name: 'FilterBarTest',
components: {
XHeader,
FilterBar,
},
data(){
return {
barMenus: barMenus
}
},
methods: {
handleShowDialog(v) {
// console.log(v);
},
handleCloseDialog(v) {
// console.log(v);
},
handleChangeTab(v) {
// console.log(v);
},
handleChangeMainItem(v) {
// console.log(v)
},
changeData(v) {
console.log(v);
}
}
}
</script> <style lang="scss" scoped>
//
</style>
data.js
export default [
{
name: '附近',
icon: '',
value: 'area',
showTabHeader: true,
defaultIcon: '',
selectIcon: '',
selectIndex: 0,
tabs: [
{
icon: '',
name: '商圈',
selectIndex: 0,
detailList: [
{
name: '附近',
icon: '',
selectIndex: 0,
list: [{
name: '默认',
value: 'all'
}, {
name: '500米',
value: '500'
}, {
name: '1000米',
value: '1000'
}]
},
{
name: '朝阳区',
icon: '',
selectIndex: 1,
list: [{
name: '全部',
value: 'all'
}, {
name: '建国门',
value: 'jianguomen'
}, {
name: '亚运村',
value: 'yayuncun'
}]
},
{
name: '海淀区',
icon: '',
selectIndex: 2,
list: [{
name: '全部',
value: 'all'
}, {
name: '中关村',
value: 'zhongguancun'
}, {
name: '五道口',
value: 'wudaokou'
}]
}
]
},
{
icon: '',
name: '地铁沿线',
selectIndex: 1,
detailList: [
{
name: '1号线',
icon: '',
selectIndex: 0,
list: [{
name: '平果圆',
value: 'pingguoyuan'
}, {
name: '古城',
value: 'gucheng'
}, {
name: '八角游乐园',
value: 'bajiaoyouleyuan'
}]
},
{
name: '2号线',
icon: '',
selectIndex: 1,
list: [{
name: '积水潭',
value: 'jishuitan'
}, {
name: '鼓楼大街',
value: 'guloudajie'
}, {
name: '安定门',
value: 'andingmen'
}]
},
{
name: '4号线',
icon: '',
selectIndex: 2,
list: [{
name: '安和桥北',
value: 'anheqiaobei'
}, {
name: '北宫门',
value: 'beigongmen'
}, {
name: '西宛',
value: 'xiwan'
}]
}
]
}
]
},
{
name: '菜系',
icon: '',
value: 'food',
showTabHeader: false,
defaultIcon: '',
selectIcon: '',
selectIndex: 0,
tabs: [
{
icon: '',
name: '',
selectIndex: 0,
detailList: [
{
name: '全部',
icon: '',
value: '全部',
selectIndex: 0,
list: [{
name: "全部",
value: 'all'
}]
},
{
name: '中餐馆',
icon: '',
value: '中餐馆',
selectIndex: 1,
list: [{
name: '全部',
value: 'all'
}, {
name: '火锅',
value: 'hot pot'
}, {
name: '川菜',
value: 'Sichuan cuisine'
}]
},
{
name: '西餐馆',
icon: '',
value: '西餐管',
selectIndex: 2,
list: [{
name: '全部',
value: 'all'
}, {
name: '披萨',
value: 'pizza'
}, {
name: '牛排',
value: 'steak'
}]
}
]
}
]
},
{
name: '排序',
icon: '',
value: 'compositor',
showTabHeader: false,
defaultIcon: '',
selectIcon: '',
selectIndex: 0,
tabs: [
{
icon: '',
name: '',
selectIndex: 0,
detailList: [
{
name: '只能排序',
icon: '',
value: '0',
selectIndex: 0
},
{
name: '离我最近',
icon: '',
value: '1',
selectIndex: 1
},
{
name: '评价最好',
icon: '',
value: '2',
selectIndex: 2
}
]
}
]
},
{
name: '筛选',
icon: '',
value: 'filter',
type: 'filter',
showTabHeader: false,
defaultIcon: '',
selectIcon: '',
selectIndex: 0,
tabs: [
{
icon: '',
name: '价格',
selectIndex: 0,
detailList: [
{
name: '0-50',
value: '0-50',
selectIndex: -1
},
{
name: '50-100',
value: '50-100',
selectIndex: -1
},
{
name: '100-150',
value: '100-150',
selectIndex: -1
},
{
name: '150-200',
value: '150-200',
selectIndex: -1
},
{
name: '200-250',
value: '200-250',
selectIndex: -1
},
{
name: '300-350',
value: '300-350',
selectIndex: -1
}
]
},{
icon: '',
name: '入住类型',
selectIndex: 1,
detailList: [
{
name: '不限',
value: 'all',
selectIndex: -1
}, {
name: '全日房',
value: 'daily',
selectIndex: -1
}, {
name: '钟点房',
value: 'time',
selectIndex: -1
},
{
name: '支持团购',
value: 'group buy',
selectIndex: -1
}
]
}
]
}]
3.效果图
.