uniapp点餐或商城系统菜单左右联动

目前市面上很多小程序或者app都出现点餐或者商城类的,这里使用到了uniapp的内置组件scroll-view

注意:

这个组件用的时候要注意需要给滚动区域设置高度,滚动才能生效

需要给二级分类添加id,并且值不能已数字开头,可以加上类似于 :id="'item-' + index"与js中的api对应

实现的效果,如图所示

因为是从完整的代码中取得,若有样式问题请自行搭建补充

搭建html

左侧菜单品类


      <!-- 左侧一级分类 -->
      <scroll-view
        scroll-y="true"
        :scroll-with-animation="false"
        class="leftColumn"
      >
        <view>
          <view
            class="menu-item"
            v-for="(good, index) in orderlist"
            :key="good.categoryCode"
            :class="{ current: index === leftCilck }"
            :style="{ color: index === leftCilck ? iconColor : '#000' }"
            @click="clickMenuItem(index)"
          >
            <image
              :showLoading="true"
              v-if="good.categoryImage"
              :src="good.categoryImage"
              class="firstLevelImage"
            ></image>
            <span v-if="good.categoryName">{{ good.categoryName }}</span>
          </view>
        </view>
      </scroll-view>
    

右侧菜单二级分类


      <!-- 右侧联动二级分类 -->
      <scroll-view
        ref="rightList"
        scroll-y="true"
        :scroll-with-animation="false"
        @scroll="mainScroll"
        :scroll-into-view="scrollInto"
      >
        <view class="'foods-wrapper'">
          <view
            class="food-list-hook"
            v-for="(item, index) in orderlist"
            :id="'item-' + index"
            :key="index"
          >
            <view class="title">{{ item.categoryName }}</view>
            <view class="box" v-for="v in item.goods" :key="v.goodsCode">
              <view class="box_left">
                <view class="box_left_img">
                  <image :src="v.imageUrl" mode="aspectFill"></image>
                </view>
              </view>
              <view class="box_right">
                <text class="box_right_title">{{ v.goodsName }}</text>
                <span class="describe" v-html="v.goodsDesc"></span>
                <view class="box_right_price">
                  <view class="box_right_price_left">
                    ¥{{ v.amount }}
                    <text class="provinciate_price">¥{{ v.price }}</text>
                  </view>
                  <!-- 多规格产品 -->
                  <view
                    v-if="v.goodsType == '2' || v.goodsType == '3'"
                    class="box_right_price_right"
                    @tap="specifications(v)"
                  >
                    <text>选规格</text>
                  </view>
                  <!-- 单品 -->
                  <u-number-box
                    :asyncChange="true"
                    v-if="v.goodsType == '1'"
                    :min="0"
                    :max="v.maxQuantity"
                    :disablePlus="shopVal == maxQuantity"
                    v-model="v.num"
                    @change="
                      (value) => {
                        menuChange(v, value);
                      }
                    "
                  >
                    <view slot="minus" class="minus">
                      <u-icon name="minus" size="12"></u-icon>
                    </view>
                    <text
                      slot="input"
                      style="text-align: center"
                      class="input"
                      >{{ v.num }}</text
                    >
                    <view slot="plus" class="plus">
                      <u-icon name="plus" color="#FFFFFF" size="12"></u-icon>
                    </view>
                  </u-number-box>
                </view>
              </view>
            </view>
          </view>
        </view>
      </scroll-view>
  

js部分-让滚动区域能够正常滚动跳转

 async onLoad() {
    await this.onRightListFetch();
    await this.initScrollView();
  }, 
methods:{
  clickMenuItem(index) {
      this.leftCilck = index;
      this.scrollInto = "item-" + index;
    },
    /* 主区域滚动监听 */
    mainScroll(e) {
      clearTimeout(this.mainThrottle);
      this.mainThrottle = setTimeout(() => {
        scrollFn();
      }, 10);
      let scrollFn = () => {
        let top = e.detail.scrollTop;
        let index = 0;
        /* 查找当前滚动距离 */
        this.topArr.forEach((item, id) => {
          if (top + 140 >= item) {
            index = id;
            this.leftCilck = index < 0 ? 0 : index;
          }
        });
      };
    },
    /* 初始化滚动区域 */
    initScrollView() {
      return new Promise((resolve, reject) => {
        let view = uni.createSelectorQuery().select(".goods");
        view
          .boundingClientRect((res) => {
            this.scrollTopSize = res.top;
            this.scrollHeight = res.height;
            setTimeout(() => {
              resolve();
            }, 100);
          })
          .exec();
      });
    },
    /* 获取元素顶部信息 */
    getElementTop() {
      new Promise((resolve) => {
        let view = uni.createSelectorQuery().selectAll(".food-list-hook");
        view
          .boundingClientRect((data) => {
            resolve(data);
          })
          .exec();
      }).then((res) => {
        let topArr = res.map((item) => {
          return item.top - this.scrollTopSize; /* 减去滚动容器距离顶部的距离 */
        });
        this.topArr = topArr;
      });
    },
 clickMenuItem(index) {
      this.leftCilck = index;
      this.scrollInto = "item-" + index;
    },
 // 获取右边数据
    async onRightListFetch() {
      this.$nextTick(() => {
        this.initScrollView();
      });
    },
}

变量部分

data() {
    return {
      mainThrottle: null,
      topArr: [],
      scrollInto: "", //左侧定位
      scrollTopSize: "",
      scrollHeight: "",
      leftCilck: 0, //左侧点击菜单
      listHeight: [], //右侧每一分类高度集合
      scrollY: 0, // 右侧滑动的Y轴坐标 (滑动过程时实时变化)
      tops: [], // 所有右侧分类li的top组成的数组  (列表第一次显示后就不再变化)
      cityInfo: null,
      menuList: [],
      indexList: [],
      selection_goods: {}, //选择规格的元素
      orderlist: [],
      mobilePhoneModel: "",
     
    };
  }

css部分

.goods {
  display: flex;
  width: 100%;
  background: #fff;
  overflow: hidden;

  font-size: 28rpx;
  .leftColumn {
    width: 160rpx;
    background: #f3f5f7;
  }
  .menu-wrapper {
    width: 160rpx;
    background: #f3f5f7;
    height: 100%;
  }
  .listMenu-wrapper {
    width: 160rpx;
    background: #f3f5f7;
    padding-bottom: 90rpx;
    box-sizing: border-box;
  }
  .menu-item {
    image {
      height: 60rpx;
      width: 60rpx;
    }
    display: flex;
    min-height: 120rpx;
    padding: 20rpx;
    box-sizing: border-box;
    line-height: 28rpx;
    align-items: center;
    justify-content: center;
    flex-direction: column;
    font-size: 24rpx;
    text-align: center;
    &.current {
      display: flex;
      align-items: center;
      background: #fff;

      font-weight: 700;
    }

    .icon {
      display: inline-block;
      vertical-align: top;
      width: 24rpxx;
      height: 24rpx;
      margin-right: 4rpx;
      background-size: 24rpx 24rpx;
      background-repeat: no-repeat;
    }

    .text {
      display: table-cell;
      width: 112rpx;
      vertical-align: middle;
      font-size: 24rpx;
    }
  }
}
.foods-wrapper {
  padding: 20rpx 30rpx;
  box-sizing: border-box;
  width: calc(100vw - 160rpx);
}
.box {
  margin-bottom: 60rpx;
  display: flex;
  position: relative;
  border-radius: 40rpx;
  .box_left {
    .box_left_img {
      width: 180rpx;
      height: 180rpx;
      position: relative;

      image {
        border-radius: 11rpx;
      }

      .box_left_img_text {
        width: 63rpx;
        height: 36rpx;
        background: #05b6f6;
        border-radius: 8rpx;
        font-size: 24rpx;
        font-weight: 400;
        color: #fdfeff;
        line-height: 36rpx;
        text-align: center;
        padding: 3rpx 5rpx;
        position: absolute;
        box-sizing: border-box;
        top: -10rpx;
        right: -5rpx;
      }
    }
  }

  ::v-deep .box_right {
    margin-left: 20rpx;
    display: flex;
    justify-content: space-around;
    flex-direction: column;
    flex: 1;
    .box_right_title {
      font-size: 30rpx;
      font-weight: 700;
      color: #353535;
      display: inline-block;
      margin: 10rpx 0;
      width: 100%;
    }

    .box_right_price {
      display: flex;
      align-items: center;
      justify-content: space-between;
      line-height: 1;
      width: 100%;
      margin-top: 14rpx;

      .box_right_price_left {
        font-size: 30rpx;
        color: #ff2b21;
        .provinciate_price {
          color: #999;
          font-size: 22rpx;
          margin-left: 12rpx;
          text-decoration: line-through;
          line-height: 46rpx;
        }
      }

      .box_right_price_right {
        width: 101rpx;
        height: 47rpx;
        border-radius: 17rpx;
        font-size: 24rpx;
        font-weight: 400;
        color: #fdfeff;
        line-height: 47rpx;
        text-align: center;
        position: relative;
      }
    }
  }
}
.minus {
  width: 22px;
  height: 22px;
  border-width: 1px;
  border-color: #e6e6e6;
  border-style: solid;
  border-top-left-radius: 100px;
  border-top-right-radius: 100px;
  border-bottom-left-radius: 100px;
  border-bottom-right-radius: 100px;
  @include flex;
  justify-content: center;
  align-items: center;
}

.input {
  padding: 0 10px;
}

.plus {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  display: flex;
  justify-content: center;
  align-items: center;
}
.soldOut {
  position: absolute;
  width: 100%;
  height: 100%;

  span {
    position: absolute;
    top: -20rpx;
    left: -20rpx;
    color: #fff;
    background-color: #d62f35;
    padding: 2rpx 6rpx;
    border-radius: 8rpx;
    box-sizing: border-box;
  }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值