注意:在模拟器用鼠标滚动是不会切换光标的,因为使用的是触摸滑动。【自定义类型贴在最后了】
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([]) //右侧显示内容(标题+文本)const topArr = ref([]) //每个锚点与到顶部距离const leftIndex = ref(0) //左边光标indexconst isMainScroll = ref(false) // 是否touch到右侧const scrollInto = ref('') //锚点/* 获取列表数据 */const getListData = async () => { const left = ref([]) const main = ref([]) categoryList.forEach((item) => { left.value.push(`${item.id + 1}类商品`) let list: orderDetail[] = [] // for (let i = 0; i { 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 { 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 { 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部分如下:
{{ item.name }} {{ item.title }} {{ goods.goodsName }} {{ goods.memo }} 限时优惠 ¥ {{ goods.price.toFixed(2) }} <view v-if="goods.oldPrice != 0 && goods.price ¥ {{ goods.oldPrice!.toFixed(2) }}
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[]