vue 自定义 移动端筛选条件

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.效果图

  

.

posted @ 2018-04-15 21:01  每天都要进步一点点  阅读(2009)  评论(0编辑  收藏  举报