1.创建组件
components/FilterBar/FilterBar.vue
<template><div class="filterbar" :style="{'top': top + 'px'}"><div class="container"><div class="row"><divclass="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) {//记录tabIndexthis.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;// debuggerif(!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><!-- 内容部分 --><FilterBartop="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.效果图
.