uniapp模仿淘宝商品列表展示

发布时间 2023-10-07 16:44:56作者: &蝶儿&

父组件代码:

<template>
<view class="boxPage">
  <scroll-view class="contentBox" :scroll-y="true" :scroll-top="scrollTopPage" :refresher-enabled="true"
            :refresher-triggered="triggered" @refresherrefresh="freshList" @refresherpulling="onPulling"
            @scrolltolower="scrolltolower">
            <view class="productBox">
                <!-- tab栏 -->
                <u-sticky bgColor="#fff" :offset-top="-48">
                    <view class="tabbar">
                        <u-tabs :list="tabList" itemStyle="padding-left: 37rpx; padding-right: 37rpx; height: 88rpx;"
                            :activeStyle="{
                        fontWeight: 'bold',
                        fontFamily: 'PingFang SC',
                        color: '#333333',
                        transform: 'scale(1.08)'
                    }" :inactiveStyle="{
                        fontFamily: 'PingFang SC',
                        fontWeight: '500',
                        color: '#666666',
                        transform: 'scale(1)'
                    }" :lineColor="'#333333'" @change="changeTab"></u-tabs>
                    </view>
                </u-sticky>
                <!-- 商品列表 -->
                <view class="content-report-box" v-if="total> 0">
                    <view class="leftBox">
                        <productList :productData="leftListData"></productList>
                    </view>
                    <view class="rightBox">
                        <productList :productData="rightListData"></productList>
                    </view>
                </view>
                <view class="noDataBox" v-if="total== 0&&loadStatus !== 'loading'">
       <!-- 设置需要的无数据图片 -->
                    <image src="@/static/images/noData.png" class="img"></image>
                    <view class="noDataText">
                        暂无产品,敬请期待……
                    </view>
                </view>
                <uni-load-more v-else :status="loadStatus"></uni-load-more>
            </view>
  </scroll-view>
  </view>
</template>
<script>
    import {
        queryProductList  //后端接口方法
    } from "@/api/api.js"
    import productList from './components/productList.vue'
    import uniLoadMore from "./components/uni-load-more.vue";
    export default {
        components: {
            productList,uniLoadMore
        },
        data() {
            return {
                activeIndex: -1, // tab栏索引
                tabList: [{ // tab栏列表 类型:-1全部
                        name: '全部',
                        value: -1
                    }
                ],
                listData: [], // 商品列表
                leftListData: [], // 左侧区域商品列表
                rightListData: [], // 右侧区域商品列表
      triggered: false,
                page: 1,
                total: 0,
      scrollTopPage: 0, //回到顶部
                loadStatus: "loading",
            };
        },
        onShow() {
            this.page = 1
            this.loadStatus = "loading";
            this.getProductList()
        },
        methods: {
   scroll(e) {
                this.scrollTopPage = e.target.scrollTop //滚动条实时位置
            },
            scrolltolower(e) {
                if (e.detail.direction == "bottom") {
                    if (this.listData.length < this.total) {
                        this.loadStatus = "loading";
                        this.page++
                        this.getProductList()
                    } else {
                        this.loadStatus = "noMore";
                    }
                }
            },
            onPulling() {
                let that = this;
                if (!this.triggered) {
                    //下拉加载,先让其变true再变false才能关闭
                    this.triggered = true;
                    //关闭加载状态 (转动的圈),需要一点延时才能关闭
                    setTimeout(() => {
                        that.triggered = false;
                    }, 1000)
                }
            },
            // 下拉刷新
            freshList() {
                this.page = 1
                this.loadStatus = "loading";
                this.getProductList()
            },
            // 获取商城商品列表
            getProductList() {
                if (this.page == 1) {
                    this.leftListData = []
                    this.rightListData = []
                    this.listData = []
                }
                let data = {
                    "limit": 10,
                    "page": this.page,
                    "type": this.activeIndex
                }
                queryProductList(data).then(res => {
                    if (res.code == 200) {
                        this.total = res.data.total
                        let listData = res.data.data
                        if (listData.length) {
                            this.listData = this.listData.concat(listData)
                            listData.forEach((item, index) => {
                                if (index % 2 == 0) {
                                    this.leftListData.push(item)
                                } else {
                                    this.rightListData.push(item)
                                }
                            })
                        }
                        if (this.page == 1 && this.total == this.listData.length) {
                            this.loadStatus = "noMore";
                        }

                    }
                })
            },
            // 切换商品列表tab
            changeTab(index) {
                this.scrollTopPage = 0 //回到顶部
                this.page = 1
                this.activeIndex = index.value
                this.loadStatus = "loading";
                this.getProductList()
            },
        }
    }
</script>
<style lang="scss" scoped>
    /deep/ .u-sticky {
        border-radius: 8px 8px 0px 0px;
    }
.boxPage {
        background-color: #fff;
        height: 100%;
        position: relative;
  .contentBox {
            // overflow-y: auto;
            height: calc(100% - 88rpx);
        }
  .productBox {
            border-radius: 16rpx 16rpx 0rpx 0rpx;
            padding: 0rpx 0rpx 100rpx;
            z-index: 999;
            background-color: #fff;
            margin-top: -10rpx;
    .tabbar {
                margin: 0rpx 23rpx 28rpx;
            }
        }
.content-report-box {
            display: flex;
            flex-wrap: wrap;
            padding: 0 20rpx;

            .leftBox {
                width: 50%;
                box-sizing: border-box;
            }

            .rightBox {
                width: 50%;
                box-sizing: border-box;
            }
        }

        .noDataBox {
            width: 100%;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;

            .img {
                width: 320rpx;
                height: 268rpx;
            }

            .noDataText {
                font-size: 28rpx;
                font-family: PingFang SC;
                font-weight: 500;
                color: #999999;
                margin-top: 48rpx;
            }
        }
}
.uni-load-more{
        position: relative;
        background-color: #fff;
        padding-bottom: 100rpx;
        padding-top: 30rpx;
    }
</style>
商品列表子组件:
<template>
    <view>
        <view class="content-report" v-for="(item,index) in productData" :key="index">
            <image class="report-image" :src="item.coverAddress" width="302"></image>
            <view class="report-detail">
                <view class="detail-report">{{item.name}}</view>
                <view class="detail-integral">
                    <view class="priceUnit">¥</view>
                    <view class="integral-total">{{item.price}}</view>
                </view>
            </view>
        </view>
    </view>
</template>
<script>
    export default {
        props: {
            productData: {
                type: Array,
                default: []
            }
        },
    }
</script>
<style lang="scss" scoped>
    .leftBox {
        .content-report {
            margin-bottom: 14rpx;
            margin-right: 7rpx;
        }
    }

    .rightBox {
        .content-report {
            margin-bottom: 14rpx;
            margin-left: 7rpx;
        }
    }
.report-image {
        width: 100%;
        height: 302rpx;
        display: block;
        border-radius: 16rpx 16rpx 0rpx 0rpx;
        background-size: 100% 100%;
    }
.report-detail {
        width: 100%;
        padding-bottom: 31rpx;
        border-radius: 0rpx 0rpx 16rpx 16rpx;
        box-shadow: 0rpx 1rpx 20rpx 0rpx rgba(112, 117, 123, 0.1);

        .detail-report {
            padding: 22rpx 22rpx 0rpx;
            font-size: 30rpx;
            font-family: PingFang SC;
            font-weight: 500;
            color: #333333;
            word-wrap: break-word; // 数字与汉字结合不换行展示
            word-break: break-all;
            -webkit-line-clamp: 2; //超出2行显示省略号
            display: -webkit-box;
            -webkit-box-orient: vertical;
            overflow: hidden;
            text-overflow: ellipsis;
        }
.detail-integral {
            display: flex;
            align-items: baseline;
            margin-left: 24rpx;
            margin-bottom: 8rpx;
            .integral-total {
                display: flex;
                justify-content: space-between;
                align-items: center;
                font-size: 32rpx;
                font-family: PingFang SC;
                font-weight: bold;
                color: #EA3F49;
            }
            .priceUnit {
                font-size: 20rpx;
                font-family: PingFang SC;
                font-weight: bold;
                margin-right: 6rpx;
                color: #E93A40;
            }
        }
}
</style>
加载更多子组件代码:
<template>
  <view class="uni-load-more" v-if="status=='more'||status=='loading' || status=='noMore' || status=='contentText.contentrefresh'">
    <view
      v-if="iconType==='circle' || iconType==='auto' && platform === 'android'"
      v-show="status === 'loading' && showIcon"
      class="uni-load-more__img"
    >
      <view
        :style="{borderColor : color}"
        class="loader-android" />
    </view>
    <view
      v-else
      v-show="status === 'loading' && showIcon"
      class="uni-load-more__img">
      <view class="load1 load">
        <view
          :style="{ background: color }"
          class="uni-load-view_wrapper" />
        <view
          :style="{ background: color }"
          class="uni-load-view_wrapper" />
        <view
          :style="{ background: color }"
          class="uni-load-view_wrapper" />
        <view
          :style="{ background: color }"
          class="uni-load-view_wrapper" />
      </view>
      <view class="load2 load">
        <view
          :style="{ background: color }"
          class="uni-load-view_wrapper" />
        <view
          :style="{ background: color }"
          class="uni-load-view_wrapper" />
        <view
          :style="{ background: color }"
          class="uni-load-view_wrapper" />
        <view
          :style="{ background: color }"
          class="uni-load-view_wrapper" />
      </view>
      <view class="load3 load">
        <view
          :style="{ background: color }"
          class="uni-load-view_wrapper" />
        <view
          :style="{ background: color }"
          class="uni-load-view_wrapper" />
        <view
          :style="{ background: color }"
          class="uni-load-view_wrapper" />
        <view
          :style="{ background: color }"
          class="uni-load-view_wrapper" />
      </view>
    </view>
    <text
      :style="{ color: color }"
      class="uni-load-more__text">
      {{ status === 'more' ? contentText.contentdown : status === 'loading' ? contentText.contentrefresh : contentText.contentnomore }}
    </text>
  </view>
</template>

<script>
const platform = uni.getSystemInfoSync().platform
export default {
  name: 'UniLoadMore',
  props: {
    status: {
      // 上拉的状态:more-loading前;loading-loading中;noMore-没有更多了
      type: String,
      default: 'more'
    },
    showIcon: {
      type: Boolean,
      default: true
    },
    iconType: {
      type: String,
      default: 'auto'
    },
    color: {
      type: String,
      default: '#777777'
    },
    contentText: {
      type: Object,
      default () {
        return {
          contentdown: '上拉显示更多',
          contentrefresh: '正在加载...',
          contentnomore: '没有更多数据了'
        }
      }
    }
  },
  data () {
    return {
      platform: platform
    }
  }
}
</script>

<style lang="scss">
.uni-load-more {
  display: flex;
  flex-direction: row;
  height: 80upx;
  align-items: center;
  justify-content: center;

  &__text {
    font-size: 28upx;
    color: $uni-text-color-grey;
  }

  &__img {
    position: relative;
    height: 24px;
    width: 24px;
    margin-right: 10px;
    & > .load {
      position: absolute;
      .uni-load-view_wrapper {
        width: 6px;
        height: 2px;
        border-top-left-radius: 1px;
        border-bottom-left-radius: 1px;
        background: $uni-text-color-grey;
        position: absolute;
        opacity: 0.2;
        transform-origin: 50%;
        animation: load 0.96s ease infinite;

        &:nth-child(1) {
          transform: rotate(90deg);
          top: 2px;
          left: 9px;
        }

        &:nth-child(2) {
          transform: rotate(180deg);
          top: 11px;
          right: 0px;
        }

        &:nth-child(3) {
          transform: rotate(270deg);
          bottom: 2px;
          left: 9px;
        }

        &:nth-child(4) {
          top: 11px;
          left: 0px;
        }
      }
    }

    & > .loader-android {
      position: absolute;
      left: 0;
      top: 0;
      right: 0;
      bottom: 0;
      box-sizing: border-box;
      border: solid 2px #777777;
      border-radius: 50%;
      border-bottom-color: transparent !important;
      animation: loader-android 1s 0s linear infinite;
    }
  }
}

.load1,
.load2,
.load3 {
  height: 24px;
  width: 24px;
}

.load2 {
  transform: rotate(30deg);
}

.load3 {
  transform: rotate(60deg);
}
.load1 .uni-load-view_wrapper:nth-child(1) {
  animation-delay: 0s;
}

.load2 .uni-load-view_wrapper:nth-child(1) {
  animation-delay: 0.08s;
}

.load3 .uni-load-view_wrapper:nth-child(1) {
  animation-delay: 0.16s;
}

.load1 .uni-load-view_wrapper:nth-child(2) {
  animation-delay: 0.24s;
}

.load2 .uni-load-view_wrapper:nth-child(2) {
  animation-delay: 0.32s;
}

.load3 .uni-load-view_wrapper:nth-child(2) {
  animation-delay: 0.40s;
}

.load1 .uni-load-view_wrapper:nth-child(3) {
  animation-delay: 0.48s;
}

.load2 .uni-load-view_wrapper:nth-child(3) {
  animation-delay: 0.56s;
}

.load3 .uni-load-view_wrapper:nth-child(3) {
  animation-delay: 0.64s;
}

.load1 .uni-load-view_wrapper:nth-child(4) {
  animation-delay: 0.72s;
}

.load2 .uni-load-view_wrapper:nth-child(4) {
  animation-delay: 0.80s;
}

.load3 .uni-load-view_wrapper:nth-child(4) {
  animation-delay: 0.88s;
}

@-webkit-keyframes load {
  0% {
    opacity: 1;
  }

  100% {
    opacity: 0.2;
  }
}

@-webkit-keyframes loader-android {
  0% {
    transform: rotate(0deg);
  }

  100% {
    transform: rotate(360deg);
  }
}
</style>