vue3+pinia 实现上拉加载更多

该文章展示了一个Vue组件`Usedcar.vue`,用于显示二手车信息,包括品牌、价格和详细车辆详情。组件使用了Pinia进行状态管理,通过Axios从API获取数据。在用户滚动时动态加载更多内容,有错误处理和图片占位功能。同时,文章涉及到CSS样式和布局设计。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Usedcar.vue

<template>
  <div class="usedcar">
    <div class="usedcar_regular" ref="regular">
      <div class="usedcar_top">
        <img src="../assets/usedcar/left.jpg" class="usedcar_top_left" />
        <div class="usedcar_top_content">
          <div class="usedcar_top_content_left">
            <div class="registration">上牌</div>
            <div>北京</div>
            <img src="../assets/usedcar/downarrow.jpg" alt="" srcset="" />
          </div>
          <input type="text" class="input" placeholder="搜索品牌车系/价格" />
          <img src="../assets/usedcar/search.jpg" class="usedcar_top_content_search" />
        </div>
        <img src="../assets/usedcar/qiehuan.jpg" class="usedcar_top_right" />
      </div>
      <div class="usedcar_title">
        <div>
          综合排序
          <img src="../assets/usedcar/down.jpg" class="usedcar_title_down" />
        </div>
        <div>
          品牌
          <img src="../assets/usedcar/down.jpg" class="usedcar_title_down" />
        </div>
        <div>
          价格
          <img src="../assets/usedcar/down.jpg" class="usedcar_title_down" />
        </div>
        <div>
          筛选
          <img src="../assets/usedcar/screen.jpg" class="usedcar_title_downscreen" />
        </div>
      </div>
    </div>
    <div class="usedcar_content" ref="page">
      <div class="usedcar_content_rate">
        <div>5万以下</div>
        <div>5-10万</div>
        <div>10-15万</div>
        <div>15-20万</div>
        <div>20万以上</div>
      </div>
      <div class="usedcar_content_details" >
        <div class="usedcar_content_details_content" v-for="item in carList" :key="item.ID" >
          <img :src="setImage(item)" class="car" @error="onError(item)" />

          <div class="usedcar_content_details_content_right">
            <div class="car-title">
              <i>{{ item.tit_con }}</i
              ><br />
              宝马17款
            </div>
            <div class="car_content">
              <div class="car_content_registration">2022年上牌</div>
              <div class="car_content_kilometer">1.2万公里</div>
            </div>
            <div class="cat_bottom">
              <div class="cat_bottom_num">
                <i>17.6w</i
                >万 首付 3.4 万
              </div>
              <!-- <div class="collect">♥</div> -->
              <img src="../assets/usedcar/collection.jpg" alt="" />
            </div>
          </div>
        </div>
      </div>
      <div class="loading">Loading...</div>
    </div>
    <div class="usedcar-shadow" v-if="shadowShow">
      加载中...
    </div>
  </div>
</template>

<script setup lang="ts">
import {useCarStore} from '@/store'
import { storeToRefs } from 'pinia';

const carStore=useCarStore();

const {setImage,onError}=carStore;

const {carList,shadowShow,page,regular}=storeToRefs(carStore);

</script>

<style scoped lang="less">
.usedcar {
  width: 100%;
  height: 100%;
  background-color: #f5f5f6;
  .usedcar-shadow{
    width:100vw;
    height:100vh;
    position:fixed;
    left:0;
    top:0;
    background:#fff;
    line-height:100vh;
    text-align:center;
  }
  .usedcar_regular {
    width: 100%;
    height: 188px;
    position: fixed;
    background-color: #f5f5f6;

    .usedcar_top {
      display: flex;
      justify-content: space-between;
      padding: 0 30px;
      box-sizing: border-box;
      height: 96px;
      width: 100%;

      align-items: center;
      .usedcar_top_left {
        height: 36px;
        width: 21px;
      }
      .usedcar_top_content {
        width: 571px;
        height: 65px;
        background-color: #ffffff;
        border-radius: 50px;
        display: flex;
        position: relative;
        .usedcar_top_content_left {
          width: 161px;
          height: 65px;
          font-size: 22px;
          line-height: 65px;
          padding-left: 24px;
          box-sizing: border-box;
          align-items: center;
          display: flex;
          .registration {
            color: #787878;
            padding-right: 10px;
          }
          img {
            width: 11px;
            height: 7px;
            padding-left: 12px;
          }
        }
        .usedcar_top_content_left::after {
          content: '';
          width: 2px;
          height: 23px;
          background-color: #ececec;
          margin-left: 14px;
        }
        .input {
          width: 360px;
          height: 65px;
          border: 0;
          outline: none;
          font-size: 21px;
          padding-left: 19px;
          box-sizing: border-box;
        }
        ::placeholder {
          color: #b9b9b9;
        }
        .usedcar_top_content_search {
          width: 25px;
          height: 25px;
          position: absolute;
          top: 19px;
          right: 25px;
        }
      }

      .usedcar_top_right {
        height: 39px;
        width: 39px;
      }
    }
    .usedcar_title {
      width: 100%;
      background-color: #fff;
      height: 92px;
      border-top-right-radius: 20px;
      border-top-left-radius: 20px;
      display: flex;
      justify-content: space-between;
      padding-right: 59px;
      padding-left: 29px;
      box-sizing: border-box;
      font-size: 24px;
      align-items: center;
      img {
        vertical-align: middle;
        margin-left: 13px;
      }
      .usedcar_title_down {
        width: 11px;
        height: 8px;
      }
      .usedcar_title_downscreen {
        width: 16px;
        height: 17px;
      }
    }
  }
  .usedcar_content {
    width: 100%;
    height: 101%;
    padding-top: 188px;
    box-sizing: border-box;
    background-color: white;
    .usedcar_content_rate {
      height: 120px;
      width: 100%;
      display: flex;
      justify-content: space-between;
      box-sizing: border-box;
      padding-left: 22px;
      div {
        width: 138px;
        height: 87px;
        border: 2px solid #ededee;
        border-radius: 25px;
        font-size: 21px;
        color: #282828;
        text-align: center;
        line-height: 87px;
        margin-right: 14px;
      }
    }
    .loading{
      padding-top: 20px;
    }
    .usedcar_content_details {
      width: 100%;
      background-color: white;
      // padding-top: 33px;
      box-sizing: border-box;
      .usedcar_content_details_content {
        display: flex;
        justify-content: space-between;
        padding-left: 18px;
        box-sizing: border-box;
        padding-right: 40px;
        padding-bottom: 60px;
        height:257px;
        .car {
          width: 263px;
          height: 197px;
        }
        .usedcar_content_details_content_right {
          width: 407px;
          height: 197px;
          display: flex;
          justify-content: space-between;
          flex-direction: column;

          .car-title {
            font-size: 23px;
            color: #1f1f1f;
            i {
              display: inline-block;
              font-style: normal;
              font-size: 25px;
              font-weight: 900px;
              margin: 10px 0;
              color: #020202;
            }
          }
          .car_content {
            margin-top: 30px;
            height: 32px;
            display: flex;
            .car_content_registration {
              width: 135px;
              height: 32px;
              border: 2px solid #dfdfdf;
              color: #575757;
              font-size: 18px;
              text-align: center;
              line-height: 32px;
              border-radius: 8px;
            }
            .car_content_kilometer {
              width: 113px;
              height: 32px;
              border: 2px solid #dfdfdf;
              color: #575757;
              font-size: 18px;
              text-align: center;
              line-height: 32px;
              border-radius: 8px;
              margin-left: 6px;
            }
          }
          .cat_bottom {
            display: flex;
            justify-content: space-between;
            align-items: flex-end;
            color: #eb6429;
            font-size: 21px;

            i {
              font-style: normal;
              font-size: 25px;
            }
            img {
              width: 32px;
              height: 28px;
            }
            // .collect {
            //   font-size: 32px;
            //   color: #e4e4e4;
            // }
          }
        }
      }
    }
  }
}
</style>

UsedCar.ts

import { defineStore } from 'pinia'
import { onMounted, reactive, toRefs } from 'vue'
import { getCarList } from '@/api'


interface iCarList {
    ID: number,
    tit_con: string,
    img_src: string,
    show?: boolean,
    height?: number
}

interface iCars {
    carList: iCarList[],
    shadowShow: boolean,
    imageCount: number,
    page: HTMLDivElement | null,
    regular:HTMLDivElement | null,//定位元素
    pageHeight: number,
    pageTop: number,
    itemHeight: number,
    clientHeight: number,
    scrollTop: number,
    pageCount: number,
    titleHeight:number
}

export const useCarStore = defineStore('car', () => {
    // const page = ref<HTMLDivElement>();
    // 所有的对象定义的地方
    const cars = reactive<iCars>({
        carList: [],
        shadowShow: true,
        imageCount: 0,
        page: null,// page父级元素
        regular:null,
        pageHeight: 0,
        pageTop: 0,
        itemHeight: 0,
        clientHeight: 0,// 页面可视区高度
        scrollTop: 0,// 页面滚动距离
        pageCount: 1,//页码累计,
        titleHeight:0
    });

    const onError=(item:iCarList)=>{
        item.img_src=`default.gif`
    }

    const setImage = (item: iCarList) => {

        if (item.show) {
            return `http://www.ibugthree.com/${item.img_src}`
        } else {
            return `http://www.ibugthree.com/default.gif`
        }

    }

    // 加载一页数据
    (async () => {

        cars.carList = (await getCarList({ page: `${cars.pageCount}` }));

        cars.carList.forEach((item, index) => {
            if (index <= 3) {
                const oImage = new Image();

                oImage.src = `http://www.ibugthree.com/${item.img_src}`;

                oImage.onload = () => {
                    if (cars.imageCount == 3) {
                        cars.shadowShow = false;


                        // console.log(cars.page!.offsetHeight, 999666)
                        
                        // 添加整个页面的高度

                        cars.titleHeight = cars.regular!.offsetHeight;

                        cars.pageTop = (cars.page!.children[0] as HTMLDivElement).offsetHeight+cars.titleHeight
                        cars.pageHeight = (cars.page!.children[1] as HTMLDivElement).offsetHeight;

                        // 求出每个块的高度
                        cars.itemHeight = cars.pageHeight / cars.carList.length;
                        

                        cars.carList.forEach((item, index) => {
                            item.height = cars.pageTop + cars.itemHeight * index;
                        });

                    }
                    cars.imageCount++;

                    item.show = true;

                }
            }
        });


        window.onscroll = async () => {
            cars.clientHeight = document.body.clientHeight || document.documentElement.clientHeight;
            cars.scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
            // 获取一页数据

            if (cars.clientHeight + cars.scrollTop === cars.page!.scrollHeight) {
                cars.pageCount++;

                const carList= (await getCarList({ page: `${cars.pageCount}` }));

                cars.carList=cars.carList.concat(carList)

                // 设置每个盒子的高度
                cars.carList.forEach((item, index) => {
                    item.height = cars.pageTop + cars.itemHeight * index;
                });

                console.log(cars.carList,999666)
            }

            // 每条数据设置图片是否显示
            cars.carList.forEach((item) => {
                if (!item.show) {
                    if ((cars.clientHeight + cars.scrollTop >= item.height!)) {
                        item.show = true;
                    }
                }
            });

        }



    })();

    // onMounted(() => {
    // })
    // reactive转成一个一个ref对象
    return {
        ...toRefs(cars),
        setImage,
        onError
    }
});

api/index.ts

import { Service } from "./request";
import {AxiosPromise} from 'axios'
interface searchConfig {
    page: number | string
    mod: string
}
interface getConfig {
    page: number | string
}


interface Item {
    ID: number
    activity: string
    after_sale: string
    authen: string
    bright_point: string
    buy_time: string
    car_price: string
    card_time: string
    color: string
    decimal: string
    detail_par: string
    dif_cards: string
    diff_coun: string
    discharge: string
    displacement: string
    down_payments: string
    drive: string
    emission_standard: string
    excellent_service: string
    fil_engine: string
    fil_gearbox: string
    fil_structor: string
    fueltype: string
    img_src: string
    insurance: string
    integer: string
    kilometre: string
    logistics: string
    manu_type: string
    mileage: string
    mon_price: string
    nal_price: string
    number: string
    output_volume: string
    qanda: string
    seatnumber: string
    service_shop: string
    shopcar: string
    state: boolean
    tit_con: string
    top: number
    trait: string
    transaction: string
    veh_age: string
    vehicle_system: string
    year_mspect: string
    status: number
    likeIconColor: string
    displayStatus: boolean
  }  

// 搜索接口
export function searchCar(config: searchConfig):Item[] {
    const params = new URLSearchParams()
 
    params.append('page', config.page as string);
    params.append('mod', config.mod);
 
    return Service({
        url: "./api/oldcar/searchCar",
        data: params
    }) as AxiosPromise & Item[]
}
 
// 列表接口
export function getCarList(config: getConfig):Item[] {
    const params = new URLSearchParams()
    params.append('page', config.page as string)
    return Service({
        url: "/api/oldcar/getCarList",
        data: params
    }) as AxiosPromise & Item[]
}

api/request.ts

import axios from "axios";
export const Service = axios.create({
    timeout: 80000, //延迟时间
    method: 'POST',
    headers: {
        "content-Type": "application/x-www-form-urlencoded",
        "pc-token": "4a82b23dbbf3b23fd8aa291076e660ec", //后端提供
    }
})
// 请求拦截
Service.interceptors.request.use(config => {
    return config
})
// 响应拦截
Service.interceptors.response.use(response => {
    return response.data
}, err => {
    console.log(err)
})

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值