手写类似于BetterScroll样式的左右联动菜单 uni-app+vue3+ts (使用了script setup语法糖)

这篇具有很好参考价值的文章主要介绍了手写类似于BetterScroll样式的左右联动菜单 uni-app+vue3+ts (使用了script setup语法糖)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

 注意:在模拟器用鼠标滚动是不会切换光标的,因为使用的是触摸滑动。【自定义类型贴在最后了】

script 部分如下:

import { onMounted } from 'vue'
import type { orderDetail } from '@/types/category'
import type { mainArr } from '@/types/main-arr'
import { nextTick, ref } from 'vue'
import { getCurrentInstance } from 'vue'

//页面加载
onMounted(async () => {
  await getListData()
})

//#region 左右联动菜单
const instance = getCurrentInstance()
//分类列表数据--可以多写几个
const categoryList = [
  {
    id: '1',
    name: '即食',
    picture: 'el-icon-chicken',
    children: [
      {
        deveicId: 1,
        memo: '泸州老窖特曲浓香型白酒',
        discount: 100,
        id: 2,
        inventory: 3,
        goodsName: '草莓',
        orderNum: 1,
        goodsPicPath: '/static/images/locate.png',
        price: 8.0,
        orderMoney: 0,
        oldPrice: 0,
        isLimitPromotion: false,
      },
    ],
  },
]

const mainArray = ref<mainArr>([]) //右侧显示内容(标题+文本)
const topArr = ref<any[]>([]) //每个锚点与到顶部距离
const leftIndex = ref(0) //左边光标index
const isMainScroll = ref<boolean>(false) // 是否touch到右侧
const scrollInto = ref('') //锚点

/* 获取列表数据 */
const getListData = async () => {
  const left = ref<string[]>([])
  const main = ref<mainArr>([])

  categoryList.forEach((item) => {
    left.value.push(`${item.id + 1}类商品`)

    let list: orderDetail[] = []
    // for (let i = 0; i < 10; i++)
    item.children.forEach((itm) => {
      list.push(itm)
    })
    main.value.push({
      title: item.name,
      list,
    })
  })
  mainArray.value = main.value
  await nextTick(() => {
    setTimeout(() => {
      getElementTop()
    }, 10)
  })
}

//获取距离顶部的高度
const getScrollTop = (selector: string) => {
  const top = new Promise((resolve, reject) => {
    let query = uni.createSelectorQuery().in(instance)
    query
      .select(selector)
      .boundingClientRect((data: any) => {
        resolve(data.top)
      })
      .exec()
  })
  return top
}

/* 获取元素顶部信息 */
const getElementTop = async () => {
  /* Promise 对象数组 */
  let p_arr: number[] = []
  /* 遍历数据,创建相应的 Promise 数组数据 */
  for (let i = 0; i < mainArray.value.length; i++) {
    const resu = await getScrollTop(`#item-${i}`)
    p_arr.push(Number(resu) - 200)
  }
  /* 主区域滚动容器的顶部距离 */
  getScrollTop('#scroll-el').then((res: any) => {
    let top = res
    // #ifdef H5
    top += 43 //因固定提示块的需求,H5的默认标题栏是44px
    // #endif

    /* 所有节点信息返回后调用该方法 */
    Promise.all(p_arr).then((data) => {
      topArr.value = data
    })
  })
}

/* 主区域滚动监听 */
const mainScroll = (e: { detail: { scrollTop: any } }) => {
  if (!isMainScroll.value) {
    return
  }
  let top = e.detail.scrollTop
  let index = -1
  if (top >= topArr.value[topArr.value.length - 1]) {
    index = topArr.value.length - 1
  } else {
    index = topArr.value.findIndex((item: any, index: number) => {
      return topArr.value[index + 1] >= top
    })
  }
  leftIndex.value = index < 0 ? 0 : index
}
/* 主区域触摸 */
const mainTouch = () => {
  isMainScroll.value = true
}
/* 左侧导航点击 */
const leftTap = (e: any) => {
  let index = e.currentTarget.dataset.index
  isMainScroll.value = false
  leftIndex.value = Number(index)
  scrollInto.value = `item-${index}`
}
//#endregion

 template部分如下:

<view class="content" >
    <view class="list_box">
      <!-- 菜单左边 -->
      <view class="left">
        <scroll-view scroll-y class="scroll">
          <view
            class="item"
            v-for="(item, index) in categoryList"
            :key="index"
            :class="{ active: index == leftIndex }"
            :data-index="index"
            @tap="leftTap($event)"
          >
            {{ item.name }}
          </view>
        </scroll-view>
      </view>
      <view class="main">
        <scroll-view
          scroll-y
          @scroll="mainScroll"
          class="scroll"
          :scroll-into-view="scrollInto"
          :scroll-with-animation="true"
          @touchstart="mainTouch"
          id="scroll-el"
          enhanced
          :show-scrollbar="false"
        >
          <view v-for="(item, index) in mainArray" class="item-first-box" :key="index">
            <view :id="'item-' + index">
              <text class="item-first-title">{{ item.title }}</text>
              <view class="item-first-content" v-for="(goods, index2) in item.list" :key="index2">
                <view class="goods-image-box">
                  <image
                    :src="goods.goodsPicPath"
                    mode="aspectFill"
                    class="goods-image"
                  />
                </view>
                <view class="meta">
                  <view>
                    <view class="name ellipsis">{{ goods.goodsName }}</view>
                    <view class="memo">{{ goods.memo }}</view>
                    <view class="activity-tips" v-if="goods.isLimitPromotion">限时优惠</view>
                  </view>
                  <view class="price">
                    <view>
                      <view class="actual">
                        <text class="symbol">¥</text>
                        <text>{{ goods.price.toFixed(2) }}</text>
                      </view>
                      <view
                        class="oldprice"
                        v-if="goods.oldPrice != 0 && goods.price < goods.oldPrice"
                      >
                        <text class="symbol">¥</text>
                        <text>{{ goods.oldPrice!.toFixed(2) }}</text>
                      </view>
                    </view>
                  </view>
                </view>
              </view>
            </view>
          </view>
          <view style="height: 80%"></view>
        </scroll-view>
      </view>
    </view>
  </view>

scss样式:

page {
  height: 100%;
  overflow: hidden;
  background: #f6f6f6;
}

.content {
  .list_box {
    display: flex;
    flex-direction: row;
    flex-wrap: nowrap;
    justify-content: flex-start;
    align-items: flex-start;
    align-content: flex-start;
    font-size: 28rpx;
    height: calc(100vh - 380rpx);

    .left {
      width: 200rpx;
      text-align: center;
      background-color: #f6f6f6;
      line-height: 100rpx;
      box-sizing: border-box;
      font-size: 32rpx;
      color: #666;
      height: 100%;

      .item {
        position: relative;

        &:not(:first-child) {
          margin-top: 1px;

          &::after {
            content: '';
            display: block;
            height: 0;
            border-top: #d6d6d6 solid 1px;
            width: 620upx;
            position: absolute;
            top: -1px;
            right: 0;
            transform: scaleY(0.5);
          }
        }

        &.active,
        &:active {
          color: #000000;
          background-color: #fff;
        }
      }
    }

    .main {
      height: 100%;
      background-color: #fff;
      padding: 0 20rpx;
      flex-grow: 1;
      box-sizing: border-box;

      .item-first-box {
        position: relative;
        padding-top: 20rpx;
        width: 100%;
      }
      .item-first-title {
        position: relative;
        margin-top: 20rpx;
      }
      .item-first-content {
        position: relative;
        padding-top: 20rpx;
        margin-bottom: 20rpx;
        height: 180rpx;

        .goods-image-box {
          width: 200rpx;
          position: relative;
          float: left;
          z-index: 999;
        }

        .goods-image {
          position: relative;
          width: 170rpx;
          height: 170rpx;
          border-radius: 10rpx;
        }

        .goods-inventory {
          width: 170rpx;
          height: 36rpx;
          border-radius: 0 0 10rpx 10rpx;
          margin-right: 20rpx;
          opacity: 60%;
          background-color: #5c9888;
          position: absolute;
          bottom: 0rpx;
          left: 0;
          font-size: 24rpx;
          color: white;
          text-align: center;
        }

        .goods-inventory-notenough {
          position: absolute;
          width: 170rpx;
          text-align: center;
          font-size: 22rpx;
          bottom: 4rpx;
          left: 0;
          color: white;
        }

        .goods-inventory-zero {
          position: absolute;
          width: 170rpx;
          text-align: center;
          font-size: 22rpx;
          bottom: 4rpx;
          left: 0;
          color: white;
        }
      }
      .meta {
        position: relative;
        display: inline;
      }

      .name {
        height: 40rpx;
        font-size: 26rpx;
        color: #444;
        font-weight: bold;
      }
      .memo {
        display: flex;
        margin-top: 6rpx;
        font-size: 22rpx;
        color: #888;
      }
      .activity-tips {
        display: flex;
        margin-top: 15rpx;
        font-size: 22rpx;
        background-color: #ffd8cb;
        color: #fc6d3f;
        border-radius: 10rpx;
        padding-left: 10rpx;
        padding-right: 10rpx;
        width: 110rpx;
      }
      .type {
        line-height: 1.8;
        padding: 0 15rpx;
        font-size: 24rpx;
        align-self: flex-start;
        border-radius: 4rpx;
        color: #888;
        background-color: #f7f7f8;
      }

      .price {
        display: flex;
        position: relative;
        margin-top: 16rpx;
        font-size: 24rpx;

        .actual {
          color: #444;
          margin-top: 2rpx;
          margin-left: 0rpx;
          float: left;
        }

        .oldprice {
          display: inline-block;
          font-size: 24rpx;
          margin-top: 2rpx;
          color: #999;
          margin-left: 10rpx;
          text-decoration: line-through;
        }
        .symbol {
          font-size: 24rpx;
        }

        .quantity {
          position: absolute;
          top: 0;
          right: 0;
          font-size: 24rpx;
          color: #444;
          z-index: 999999999;
        }
      }

      .right-scroll:last-child {
        border-bottom: 0;
      }
    }

    .scroll {
      height: 100%;
    }
  }
}

 category.d.ts

/** 通用商品类型 */
export type GoodsItem = {
  deveicId?: number
  /** 商品描述 */
  memo: string
  /** 商品折扣 */
  discount: number
  /** id */
  id: number
  /**库存 */
  inventory: number
  /** 商品名称 */
  goodsName: string
  /** 商品已下单数量 */
  orderNum: number
  /** 商品图片 */
  goodsPicPath: string
  /** 商品价格 */
  price: number
  /** 商品原价格 */
  oldPrice?: number
  /**促销id */
  promotionDetialId?: number
  /**是否是限时优惠 */
  isLimitPromotion: boolean
  orderMoney:number
  oldPrice:number
}

main-arr.d.ts

export type main = {
  title: string
  list: orderDetail[]
}

export type mainArr = main[]

 文章来源地址https://www.toymoban.com/news/detail-748216.html

到了这里,关于手写类似于BetterScroll样式的左右联动菜单 uni-app+vue3+ts (使用了script setup语法糖)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 微信小程序 uniapp 电商项目使用scroll-view实现左右菜单联动,点击菜单子分类联动对应商品

    最近写了个微信小程序项目,一开始不理解scroll-view用法,用的另外一种方法写的,虽然实现了效果,但是代码层面来说,不大合理,后来又通过努力,用scroll-view实现了效果。现写个文章做个记录,方便自己和大家学习记录。 效果图请看第一张。布局:左右布局,右边又分

    2024年02月14日
    浏览(80)
  • 左右联动布局效果

     效果图:

    2024年02月21日
    浏览(38)
  • 小程序商品分类页面滑动左右联动

    系列文章目录 前言 一、vtabs是什么? 二、使用步骤 1.json引入 2.wxml中使用 3.js中代码 总结 商品分类页面,左边分类及右边商品左右联动 因为微信小程序scroll-view没有h5锚点控制联动,并且需求是根据整个页面的滚动条来控制联动,所以使用了页面滚动事件onPageScroll来获取页面

    2024年02月12日
    浏览(46)
  • 微信小程序 简单的实现左右内容联动

    scroll-view 的属性 scroll-into-view 可以实现类似于瞄点链接的效果,在绑定的属性修改时会触发,滑动到对应id的地方 注意: id不能以数字开头 设置两个变量 tabIndex 、 nowIndex 保存状态,如果只设置一个更新变量时会触发瞄点更新 通过 tabIndex 更新瞄点 通过 nowIndex 设置当前的分类

    2024年02月03日
    浏览(68)
  • 微信小程序点单左右联动的效果实现

    微信小程序点单左右联动的效果实现 原理解析:   点击左边标签会跳到右边相应位置:点击改变rightCur值,转跳相应位置滑动右边,左边标签会跳到相应的位置:监听并且设置每个右边元素的top和bottom,再判断当前滑动高度在那个元素之间,再改变左边的标签的tabCur,并且

    2024年02月06日
    浏览(51)
  • vue:菜单栏联动内容页面tab

    需要实现效果:左侧菜单栏与右侧内容部分联动,当点击左侧的菜单,右侧会展示对应的tab,没有点击时,不展示(如刚进入页面没有点击菜单,则没有tab);点击后没有关闭tab再打开其他菜单(如测试项目2),则测试项目2的tab高亮为选中状态。 实现效果: 1.环境:vue、

    2024年01月20日
    浏览(69)
  • vue2 实现后台管理系统左侧菜单联动实现 tab根据路由切换联动内容,并支持移动端框架

    效果图: pc端  移动端    由于代码比较多,我这里就不一一介绍了,可以去我的git上把项目拉下来 git地址https://gitee.com/Flechazo7/htglck.git 后台我是用node写的有需要的可以评论联系

    2024年02月16日
    浏览(45)
  • javascript实现自定义右键菜单(绑定鼠标左右键)

    思路: 1.绑定右键函数。 2.获取鼠标右键按下位置的x坐标(到左面的距离),y坐标(到上面的距离)。 3.获取滚动条向下滚动距离,获取滚动条向左滚动距离 4.最后+‘px’,补全单位,添加到元素style属性,将元素移动到鼠标右键位置 1绑定右键函数 2.获取鼠标右键按下位置的

    2024年02月09日
    浏览(37)
  • vue3 + TS + elementplus + pinia实现后台管理系统左侧菜单联动实现 tab根据路由切换联动内容

    效果图:  home.vue页面代码 left.vue页面代码 tab.vue页面代码 pinia里面的代码 安装 使用插件  在main.ts中注册 路由代码 我把代码放git上了,有需要的自行拉取 https://gitee.com/Flechazo7/vue3.git

    2024年02月09日
    浏览(50)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包