微信小程序实现左右联动的菜单列表

实现效果如下:


效果展示.gif

实现左右联动的菜单列表,主要依靠scroll-view的是三个属性:
scroll-top:设置竖向滚动条位置(可视区域最顶部到scroll-view顶部的距离);
scroll-into-view:值应为某子元素id(id不能以数字开头)。设置哪个方向可滚动,则在哪个方向滚动到该元素;
bindscroll:滚动时触发,event.detail = {scrollLeft, scrollTop, scrollHeight, scrollWidth, deltaX, deltaY}

结构图示:


布局.png

wxml:

<!-- tabs -->
<view class='tabs font28 color9'>
  <view class='tab-item {{currentTab==0?"tab-active":""}}' bindtap='changeTab' data-pos='0'>选择项目</view>
  <view class='tab-item {{currentTab==1?"tab-active":""}}' bindtap='changeTab' data-pos='1'>选择技师</view>
  <view class='tab-item {{currentTab==2?"tab-active":""}}' bindtap='changeTab' data-pos='2'>优惠券</view>
</view>

<!-- 选择项目 -->
<view wx:if="{{currentTab==0}}" class='cont-pro'>
  <!-- 左侧列表 -->
  <view class='pro-left font28 color9'>
    <view wx:for="{{serviceTypes}}" class='pro-title {{index==currentLeft?"font30 color3 bgWhite":""}}' bindtap='proItemTap' data-pos='{{index}}'>{{item.type}}</view>
  </view>
  <!-- 右侧列表 -->
  <scroll-view class='pro-right' scroll-y scroll-with-animation="true" scroll-into-view="{{selectId}}" bindscroll="scrollEvent" scroll-top="{{scrollTop}}">
    <!-- id要用来实现点击左侧右侧滚动至相应位置的效果;class(pro-box)要用来计算右侧对应左侧某一分类的高度 -->
    <!-- id: item0, item1, item2... (注意:不能直接使用数字或汉字做id)-->
    <view id='{{"item"+index}}' class='pro-box' wx:for="{{serviceTypes}}" wx:for-index="index" wx:for-item="item">
      <!-- 右侧列表里的标题,高度为50px -->
      <view class="item-title font30">{{item.type}}</view>
      <view class='pro-item' wx:for="{{item.services}}" wx:for-index="idx" wx:for-item="itemName">
        <image class='pro-img' src='{{itemName.img}}'></image>
        <view class='pro-text'>
          <view class='item-name color3 font32'>{{itemName.name}}</view>
          <view class='pro-tag'>
            <text wx:for="{{itemName.label}}" wx:for-item="tag">{{tag}}</text>
          </view>
          <view class='pro-bottom'>
            <text style='color:#C93131;' class='font32'>¥{{itemName.price}}</text>
            <view class='count font30 color6'>
              <text catchtap='subCount' data-pos='{{idx}}' data-index='{{index}}' data-sid='{{itemName.id}}'>-</text>
              <text class='color3'>{{itemName.count?itemName.count:0}}</text>
              <text catchtap='addCount' data-pos='{{idx}}' data-index='{{index}}' data-sid='{{itemName.id}}'>+</text>
            </view>
          </view>
        </view>
      </view>
    </view>
  </scroll-view>
</view>

<!-- 选择技师 -->
<view wx:if="{{currentTab==1}}" class='staff'>
  ...
</view>

<!-- 优惠券-->
<view wx:if="{{currentTab==2}}" class='coupon'>
  ...
</view>

js:

var app = getApp();
Page({
  //右侧分类的高度累加数组
  //比如:[洗车数组的高度,洗车+汽车美容的高度,洗车+汽车美容+精品的高度,...]
  heightArr: [],
  //记录scroll-view滚动过程中距离顶部的高度
  distance: 0,

  /**
   * 页面的初始数据
   */
  data: {
    currentTab: 0,  //选择项目、选择技师、优惠券
    currentLeft: 0, //左侧选中的下标
    selectId: "item0",  //当前显示的元素id
    scrollTop: 0, //到顶部的距离
    serviceTypes: [], //项目列表数据
    staffList: [],
    coupons: []
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function(options) {
    this.request();
  },

  //请求列表数据
  request() {
    app.HttpClient.request({url: "services"}).then((res) => {
      console.log(res);
      this.setData({
        serviceTypes: res.data.serviceTypes,
        staffList: res.data.staffList,
        coupons: res.data.coupons
      });
      this.selectHeight();
    })
  },

  //选择项目左侧点击事件 currentLeft:控制左侧选中样式  selectId:设置右侧应显示在顶部的id
  proItemTap(e) {
    this.setData({
      currentLeft: e.currentTarget.dataset.pos,
      selectId: "item" + e.currentTarget.dataset.pos
    })
  },

  //计算右侧每一个分类的高度,在数据请求成功后请求即可
  selectHeight() {
    let that = this;
    this.heightArr = [];
    let h = 0;
    const query = wx.createSelectorQuery();
    query.selectAll('.pro-box').boundingClientRect()
    query.exec(function(res) {
      res[0].forEach((item) => {
        h += item.height;
        that.heightArr.push(h);
      })
      console.log(that.heightArr);
      // [160, 320, 1140, 1300, 1570, 1840, 2000]
      // 160:洗车标题高度50px,item的高度110,洗车只有一个item,所以50+110*1=160px;
      // 320: 汽车美容标题高度50px,只有一个item,再加上洗车的高度,所以50+110*1+160=320px;
      // ...
    })
  },

  //监听scroll-view的滚动事件
  scrollEvent(event) {
    if (this.heightArr.length == 0) {
      return;
    }
    let scrollTop = event.detail.scrollTop;
    let current = this.data.currentLeft;
    if (scrollTop >= this.distance) { //页面向上滑动
      //如果右侧当前可视区域最底部到顶部的距离 超过 当前列表选中项距顶部的高度(且没有下标越界),则更新左侧选中项
      if (current + 1 < this.heightArr.length && scrollTop >= this.heightArr[current]) {
        this.setData({
          currentLeft: current + 1
        })
      }
    } else { //页面向下滑动
      //如果右侧当前可视区域最顶部到顶部的距离 小于 当前列表选中的项距顶部的高度,则更新左侧选中项
      if (current - 1 >= 0 && scrollTop < this.heightArr[current - 1]) {
        this.setData({
          currentLeft: current - 1
        })
      }
    }
    //更新到顶部的距离
    this.distance = scrollTop;
  }
})

数据结构:


数据结构.png

如果你还想实现从其他页面,点击按钮跳转到当前页面,并且列表滚动到指定项,此项在可视区域的第一个展示:

  //优惠券点击事件
  couponTap(e) {
    let item = e.currentTarget.dataset.item;
    if (item.limitServiceName) {
      this.setData({
        currentTab: 0
      })
      this.scrollTo(item.limitServiceName);
    }
  },

  //滚动到指定名称的某一项(通过列表的商品name来判断,也可以用id或者其他的,只要是列表项的唯一标志)
  scrollTo(name) {
    let that = this;
    const query = wx.createSelectorQuery()
    query.select(".pro-item").boundingClientRect()
    //计算每一个item的高度(右侧分类的小标题高度是在css里写死的50px)
    query.exec(function(res) {
      that.moveHeight(res[0].height, name);
    })
  },

  moveHeight(height, name) {
    let list = this.data.serviceTypes;
    let top = 50; //右侧每一分类的标题名称的高度为50px,top记录每一个标题到顶部的距离
    for (let i = 0; i < list.length; i++) {
      for (let j = 0; j < list[i].services.length; j++) {
        //如果当前的item是要滚动到顶部的,
        if (list[i].services[j].name == name) {
          this.setData({
            scrollTop: height * j + top
          })
          break;
        }
      }
      //右侧每划过一个分类,就把此分类的高度和标题的高度累加到top上
      top = top + list[i].services.length * height + 50;
    }
  }
moveTo.gif

wxss:

.cont-pro {
  height: 100%;
  display: flex;
  background-color: #fff;
}

.pro-left {
  width: 160rpx;
  flex-basis: 160rpx;
  background-color: #f6f6f6;
  overflow-y: scroll;
}

.pro-title {
  width: 100%;
  height: 100rpx;
  line-height: 100rpx;
  text-align: center;
}

.pro-right {
  flex: 1;
  background-color: #fff;
  overflow-y: scroll;
}

.item-title {
  width: 100%;
  height: 50px;
  line-height: 100rpx;
  padding: 0 30rpx;
  box-sizing: border-box;
}

.item-name {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 1;
  overflow: hidden;
  word-break: break-all;
}

.pro-item {
  width: 100%;
  display: flex;
  padding: 30rpx;
  box-sizing: border-box;
}

.pro-img {
  width: 160rpx;
  height: 160rpx;
  flex-basis: 160rpx;
  flex-shrink: 0;
  border-radius: 4rpx;
  margin-right: 30rpx;
  background-color: #f5f5f5;
}

.pro-text {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}

.pro-tag {
  color: #f08d31;
  font-size: 22rpx;
}

.pro-tag text {
  padding: 4rpx 10rpx;
  background-color: rgba(240, 141, 49, 0.15);
  margin-right: 10rpx;
  border-radius: 2rpx;
}

.pro-bottom {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.count {
  width: 170rpx;
  flex-basis: 170rpx;
  background-color: #f6f6f6;
  border-radius: 28rpx;
  display: flex;
}

.count text {
  flex: 1;
  text-align: center;
  line-height: 50rpx;
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容