Vue3 Demo—— fyShop(上)

445 阅读5分钟

1 初始化

1 目录结构初始化

设置目录别名

vue.config.js >>

module.exports ={
  configureWebpack:{
    resolve:{
      alias:{
        'assets':'@/assets',
        'components':'@/components',
        'network':'@/network',
        'utils':'@/utils',
        'view':'@/view',

      }
    }
  },
  publicPath:'./'
}

home.vue >>

<template>
  <div class="home">
    <img src="assets/images/1.png" alt="">
    <img :src="imgsrc" alt="">
    <HelloWorld msg="Welcome to Your Vue.js App"/>
  </div>
</template>

<script>
// @ is an alias to /src
import HelloWorld from 'components/HelloWorld.vue'

export default {
  name: 'Home',
  data(){
    return{
      imgsrc:require('assets/images/1.png')
    }
  },
  components: {
    HelloWorld
  }
}
</script>
<style>
</style>

2 初始化公共样式

base.css >>

@import "normalize.css";

:root{
    --color-text:#666;
    --color-high-text:#42bBaa;
    --color-tint:#42b980;
    --color-background:#FFFFFF;
    --font-size:14px;
    --line-height: 1.5;
}
*,*::before,*::after{
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}
body{
    user-select: none;
    /*background-color: #FFFFFF;*/
    background: var(--color-background);
    color: var(--color-text);
    width: 100vw;
}
a{
    color: var(--color-text);
    text-decoration: none;
}
.left{
    float: left;
}
.right{
    float: right;
}

home.vue >>

<template>
  <div class="home">

    <div class="demo1">this is a test</div>


  </div>
</template>

<script>
// @ is an alias to /src
import HelloWorld from 'components/HelloWorld.vue'

export default {
  name: 'Home',
  data(){
    return{
      imgsrc:require('assets/images/1.png')
    }
  },
  components: {
    HelloWorld
  }
}
</script>
<style>
  .demo1{
    color: var(--color-high-text);
  }
</style>

截屏2021-07-29 10.01.41.png

3 初始化项目封装网络请求

(通用的网络请求位置,需要安装aixos)

cnpm i axios -S

request.js >>

import axios from 'axios';

export function request(config) {
  const instance = axios.create({
    baseURL:'https://api.shop.eduwork.cn',
    timeout:5000
  })
//  请求拦截
  instance.interceptors.request.use(config=>{
    //如果有一个接口需要认证才可以访问,就在此统一设置,(token)

    //直接放行
    return config
  },error => {

  })

//  响应拦截
  instance.interceptors.response.use(res=>{
    console.log(res)
    return res.data ? res.data : res
  },error => {
  //如果需要授权才可以访问的接口,统一去login授权

  //  如果有错误,在这里处理,根据状态码判断错误类型,
  })
  return instance(config)

}

home.js >>

import {request} from "./request";

export function getHomeAllData() {
  return request({
    url:'/api/index',
    /*method:'get'//默认是get方法
    params:{
     }
     */
  })
}

export function getBanner() {

}

home.vue >>

<template>
  <div class="home">
    {{banner}}
  </div>
</template>

<script>
import {ref,onMounted} from 'vue'
import {getHomeAllData,getBanner} from 'network/home'
export default {
  name: 'Home',
  setup(){
    const banner = ref([])
    onMounted(()=>{
      getHomeAllData().then(res=>{
        banner.value = res.slides
      }).catch(err=>{
      })
    })
    return{
      banner
    }
  }

}
  components: {

}
</script>
<style>

</style>

2 项目-首页

1 项目的导航菜单制作 tab-bar

截屏2021-07-29 12.17.25.png

route/index.js >>

import { createRouter, createWebHistory } from 'vue-router'
const Home = () => import('../views/home/Home.vue')
const Category = () => import('../views/category/Category.vue')
const Detail = () => import('../views/detail/Detail.vue')
const Profile = () => import('../views/profile/Profile.vue')
const ShopCart = () => import('../views/shopcart/ShopCart.vue')

//写路由
const routes = [
  {
    path: '',
    name: 'DefaultHome',
    component: Home
  },
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/category',
    name: 'Category',
    component: Category
  },
  {
    path: '/detail',
    name: 'Detail',
    component: Detail
  },{
    path: '/profile',
    name: 'Profile',
    component: Profile
  },{
    path: '/shopcart',
    name: 'ShopCart',
    component: ShopCart
  }
]

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
})

export default router

iconfont.css >>

@font-face {
  font-family: "iconfont"; /* Project id 2708446 */
  src: url('iconfont.woff2?t=1627537087958') format('woff2'),
       url('iconfont.woff?t=1627537087958') format('woff'),
       url('iconfont.ttf?t=1627537087958') format('truetype');
}

.iconfont {
  font-family: "iconfont" !important;
  font-size: 16px;
  font-style: normal;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

.icon-gerenzhongxin:before {
  content: "\e60b";
}

.icon-fenlei:before {
  content: "\ef11";
}

.icon-shouye_huaban1:before {
  content: "\e60c";
}

.icon-gouwuche_huaban1:before {
  content: "\e614";
}

App.vue >>

<template>

  <router-view/>
  <div id="nav">
    <router-link class="tab-bar-item" to="/">
      <div class="icon"><img src="~assets/images/首页.png" height="20" width="20"/></div>
      <div>首页</div>
    </router-link>

    <router-link class="tab-bar-item" to="/category">
      <div class="icon"><img src="~assets/images/分类.png" height="20" width="20"/></div>
      <div>分类</div>
    </router-link>

    <router-link class="tab-bar-item" to="/shopcart">
      <div class="icon"><img src="~assets/images/购物车.png" height="20" width="20"/></div>
      <div>购物车</div>
    </router-link>

    <router-link class="tab-bar-item" to="/profile">
      <div class="icon"><img src="~assets/images/个人中心.png" height="20" width="20"/></div>
      <div>我的</div>
    </router-link>
  </div>
</template>

<style lang="scss">
  @import "assets/css/base.css";
  @import "assets/css/icon/iconfont.css";

#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}

#nav {
  background-color: #F6F6F6;
  display: flex;
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  box-shadow: 0 -2px 1px rgba(100,100,100,0.1);

  a {
    color: var(--color-text);

    &.router-link-exact-active {
      /*color: #42b983;*/
      color: var(--color-high-text);
    }
  }
  .tab-bar-item{
    flex: 1;
    text-align: center;
    height: 50px;
    font-size: var(--font-size);

  }
  .tab-bar-item .icon{
    width: 24px;
    height: 24px;
    margin-top: 5px;
    vertical-align: middle;
    display: inline-block;
  }

}
</style>

截屏2021-07-30 16.29.32.png

2 项目标题栏(通过slot插槽)

其中考虑不同的回退方式和布局 截屏2021-07-29 14.57.30.png

(1)局部导航首位: component/common/navbar/NavBar.vue>>

<template>
  <div class="nav-bar">
    <div class="left" @click="goback"><slot name="left">
      <img src="~assets/images/left.png" alt="">
    </slot></div>
    <div class="center"><slot>FY-SHOP</slot></div>
    <div class="right"><slot name="right"></slot></div>
  </div>
</template>

<script>
  import {useRouter} from 'vue-router'
  export default {
    name: "NavBar",
    setup(){
      const router = useRouter()
      const goback = () =>{
        router.go(-1)
      }
      return {
        goback
      }

    }
  }
</script>

<style scoped>
.nav-bar{
  display: flex;
  background-color: var(--color-high-text);
  color: #fffdef;
  position: fixed;
  left: 0;
  right: 0;
  top: 0;
  z-index: 9;
  height: 45px;
  line-height: 45px;
  text-align: center;
  box-shadow: 0 2px 0px rgba(100,100,100,0.1);
}
.left,.right{
  width: 60px;
}
.left img{
  width: 45px;
  padding: 12px;

}
.center{
  flex: 1;
}

</style>

home.vue >>

(其他四个同样)

<template>
  <div>
    <nav-bar>
      <template v-slot:default>图书兄弟</template>
    </nav-bar>
  </div>
</template>

<script>
import NavBar from "../../components/common/navbar/NavBar";
export default {
  name: 'Home',
  components: {
    NavBar
  }
}
</script>

(2)全局导航首位 router/index.js >>

import { createRouter, createWebHistory } from 'vue-router'
const Home = () => import('../views/home/Home.vue')
const Category = () => import('../views/category/Category.vue')
const Detail = () => import('../views/detail/Detail.vue')
const Profile = () => import('../views/profile/Profile.vue')
const ShopCart = () => import('../views/shopcart/ShopCart.vue')

//写路由
const routes = [
  {
    path: '',
    name: 'DefaultHome',
    component: Home,
    meta:{
      title:'图书兄弟'
    }
  },
  {
    path: '/',
    name: 'Home',
    component: Home,
    meta:{
      title:'图书兄弟'
    }
  },
  {
    path: '/category',
    name: 'Category',
    component: Category,
    meta:{
      title:'图书兄弟-商品分类'
    }
  },
  {
    path: '/detail',
    name: 'Detail',
    component: Detail,
    meta:{
      title:'图书兄弟-商品详情'
    }
  },{
    path: '/profile',
    name: 'Profile',
    component: Profile,
    meta:{
      title:'图书兄弟-个人中心'
    }
  },{
    path: '/shopcart',
    name: 'ShopCart',
    component: ShopCart,
    meta:{
      title:'图书兄弟-购物车'
    }
  }
]

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
})

router.beforeEach((to,from,next)=>{
    //如果没有登录,在这里到login
    next()
  document.title = to.meta.title
}
  )
export default router

3 首页推荐商品组件

banner 图和推荐商品

截屏2021-07-29 15.19.22.png

截屏2021-07-29 15.18.48.png

home.js >>

import {request} from "./request";

export function getHomeAllData() {
  return request({
    url:'/api/index',

  })
}

Home.vue >>

<template>
  <div>
    <nav-bar>
      <template v-slot:default>首页</template>
    </nav-bar>

    <div class="banners">
      <img src="~assets/images/img.png" alt="">
    </div>

    <recommend-view :recommends="recommends"></recommend-view>


  </div>
</template>

<script>
import NavBar from "../../components/common/navbar/NavBar";
import RecommendView from "./ChildComps/RecommendView";
import {getHomeAllData} from "../../network/home";
import {ref,reactive,onMounted} from 'vue';

export default {
  name: 'Home',
  setup(){

    const recommends = ref([])


    onMounted(()=>{

      getHomeAllData().then(res=>{
        console.log(res.goods)
        recommends.value = res.goods.data
      })
    })
    return{
      recommends
    }
  },
  components: {
    NavBar,
    RecommendView,

  }
}
</script>
<style scoped>
  .banners img{
    width: 100%;
    height: auto;
    margin-top: 45px;
  }

</style>

RecommendsView.vue >>

<template>
  <div class="recommend">
    <div class="recommend-item" v-for="item in recommends.slice(0,4)" :key="item.id">
      <a href="" @click.prevent="goDetail(item.id)">
        <img :src="item.cover_url" alt="">
        <div>{{item.title}}</div>
      </a>
    </div>
  </div>
</template>

<script>
  import {useRouter} from 'vue-router'

  export default {
    name: "RecommendView",
    props:{
      recommends: {
        type:Array,
        default(){
          return[];
        }
      }
    },
    setup(){
      const router = useRouter()

      const goDetail = (id) =>{
        router.push({path:'/detail',query:{id}})
      }
      return{
        goDetail
      }
    }
  }
</script>

<style scoped lang="scss">
  .recommend{
    display: flex;
    width: 100%;
    text-align: center;
    padding: 15px 0 30px;
    border-bottom: 8px solid #eeeeee;
    font-size: 12px;
  }
  .recommend-item{
    flex: 1;
    img{
      width: 70px;
      height: 50px;
      margin-bottom: 10px;
    }
  }
</style>

最后跳转到Detail.vue >> (注意:点击那本书,对应Detail页面banner显示对应的id号)

<template>
  <div>
    <nav-bar>
      <template v-slot:default>商品详情:{{id}}</template>
    </nav-bar>
  </div>
</template>

<script>
  import NavBar from "../../components/common/navbar/NavBar";
  import {useRoute} from 'vue-router'
  import {ref} from 'vue'
  export default {
    name: "Detail",
    components: {
      NavBar
    },
    setup(){
      const route  = useRoute()
      let id = ref(0)
      id.value = route.query.id
      return{
        id
      }
    }
  }
</script>

<style scoped>

</style>

截屏2021-07-29 22.17.58.png

4 首页选项卡组件

截屏2021-07-30 15.24.01.png TabControl.vue >>

<template>
  <div class="tab-control">
      <div v-for="(item,index) in titles"
           :key="index"
           @click="itemClick(index)"
           class="tab-control-item" :class="{active:index == currentIndex}">
          <span>{{item}}</span>
      </div>
  </div>
</template>

<script>
  import {ref} from 'vue'

  export default {
    name: "TabControl",
    props:{
      titles:{
        type:Array,
        default() {
          return [];
        }
      }
    },
    setup(props,{emit}){
      let currentIndex = ref(0)

      const itemClick = (index) =>{
        currentIndex.value = index
        emit('tabClick',index)
      }

      return{
        currentIndex,
        itemClick
      }
    }
  }
</script>

<style scoped lang="scss">
  .tab-control{
    display: flex;
    height: 40px;
    line-height: 40px;
    text-align: center;
    font-size: 14px;
    background-color: white;
    width: 100%;

    position: sticky;
    top:44px;

    .tab-control-item{
      flex: 1;
      .span{
        padding: 6px;
      }
    }
    .active{
      color: var(--color-tint);
      span{
        border-bottom: 3px solid var(--color-tint);
      }
    }
  }

</style>

Home.vue >>

<template>
  <div>
    <nav-bar>
      <template v-slot:default>首页</template>
    </nav-bar>

    <div class="banners">
      <img src="~assets/images/img.png" alt="">
    </div>

    <recommend-view :recommends="recommends"></recommend-view>

    <tab-control @tabClick="tabClick" :titles="['畅销','new','精选']"></tab-control>
    {{temid}}<br>

  </div>
</template>

<script>
import NavBar from "../../components/common/navbar/NavBar";
import RecommendView from "./ChildComps/RecommendView";
import TabControl from "../../components/content/tabControl/TabControl";
import {getHomeAllData} from "../../network/home";
import {ref,reactive,onMounted} from 'vue';

export default {
  name: 'Home',
  setup(){
    //临时变量
    let temid = ref(0)

    const recommends = ref([])


    onMounted(()=>{

      getHomeAllData().then(res=>{
        console.log(res.goods)
        recommends.value = res.goods.data
      })
    })

    const tabClick = (index)=>{
      temid.value = index
    }
    return{
      recommends,
      temid,
      tabClick
    }
  },
  components: {
    NavBar,
    RecommendView,
    TabControl,

  }
}
</script>
<style scoped>
  .banners img{
    width: 100%;
    height: auto;
    margin-top: 45px;
  }

</style>

截屏2021-07-30 15.23.38.png

5 列表组件原型图

截屏2021-07-30 16.10.30.png GoodsListItem.vue >>

<template>
  <div class="good-item">
    <img src="~assets/images/1.png" alt="">
    <div class="goods-info">
      <p>标题</p>
      <span class="price"><small>$</small>100</span>
      <span class="collect">9</span>
    </div>

  </div>
</template>

<script >
  export default {
    name: "GoodsListItem"
  }
</script>

<style scoped lang="scss">
  .good-item{
    width: 45%;
    padding-bottom: 40px;
    position: relative;

    img{
      width: 100%;
      border-radius: 5%;

    }
    .goods-info{
      font-size: 12px;
      /*居中*/
      position: absolute;
      bottom: 5px;
      left: 0;
      right: 0;
      overflow: auto;
      text-align: center;


      p{
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
        margin-bottom: 3px;

      }
      .price{
        color: darkred;
        margin-right: 20px;

      }
      .collect{
        position: relative;

      }
      .collect::before{
        center:'';
        position: absolute;
        left: -15px;
        width: 14px;
        height: 14px;
        top:-1;
        background:url('~assets/images/collect.png')0 0 /14px 14px;
      }

    }
  }

  </style>

GoodsList.vue >>

<template>
  <div class="goods">
    <goods-list-item></goods-list-item>
    <goods-list-item></goods-list-item>
    <goods-list-item></goods-list-item>
    <goods-list-item></goods-list-item>
    <goods-list-item></goods-list-item>
    <goods-list-item></goods-list-item>
    <goods-list-item></goods-list-item>
    <goods-list-item></goods-list-item>
    <goods-list-item></goods-list-item>
    <goods-list-item></goods-list-item>
    <goods-list-item></goods-list-item>
    <goods-list-item></goods-list-item>


  </div>
</template>

<script>
  import GoodsListItem from "./GoodsListItem";
  export default {
    name: "GoodsList",
    components:{
      GoodsListItem
    }

  }
</script>

<style scoped>
  .goods{
    display: flex;
    flex-wrap: wrap;
    justify-content: space-around;
  /*  四周环绕*/
    padding: 5px;
  }

</style>

Home.vue >>

<template>
  <div>
    <nav-bar>
      <template v-slot:default>首页</template>
    </nav-bar>

    <div class="banners">
      <img src="~assets/images/img.png" alt="">
    </div>

    <recommend-view :recommends="recommends"></recommend-view>

    <tab-control @tabClick="tabClick" :titles="['畅销','new','精选']"></tab-control>

    <goods-list></goods-list>
    {{temid}}<br>


  </div>
</template>

<script>
import NavBar from "../../components/common/navbar/NavBar";
import RecommendView from "./ChildComps/RecommendView";
import TabControl from "../../components/content/tabControl/TabControl";
import GoodsList from "../../components/content/goods/GoodsList";
import {getHomeAllData} from "../../network/home";
import {ref,reactive,onMounted} from 'vue';

export default {
  name: 'Home',
  setup(){
    //临时变量
    let temid = ref(0)

    const recommends = ref([])


    onMounted(()=>{

      getHomeAllData().then(res=>{
        console.log(res.goods)
        recommends.value = res.goods.data
      })
    })

    const tabClick = (index)=>{
      temid.value = index
    }
    return{
      recommends,
      temid,
      tabClick
    }
  },
  components: {
    NavBar,
    RecommendView,
    TabControl,
    GoodsList

  }
}
</script>
<style scoped>
  .banners img{
    width: 100%;
    height: auto;
    margin-top: 45px;
  }

</style>

6 商品列表绑定接口数据

截屏2021-07-30 17.13.14.png Home.vue >>

<template>
  
</template>

<script>
import NavBar from "../../components/common/navbar/NavBar";
import RecommendView from "./ChildComps/RecommendView";
import TabControl from "../../components/content/tabControl/TabControl";
import GoodsList from "../../components/content/goods/GoodsList";
import {getHomeAllData,getHomeGoods} from "../../network/home";
import {ref,reactive,onMounted} from 'vue';

export default {
  name: 'Home',
  setup(){
    //临时变量
    let temid = ref(0)

    const recommends = ref([])

    //商品列表数据模型
    const goods = reactive({
      sales:{page:0,list:[]},
      recommend:{page:0,list:[]},
      new:{page:0,list:[]},

    })
    onMounted(()=>{

      getHomeAllData().then(res=>{
        // console.log(res.goods)
        recommends.value = res.goods.data
      })

      getHomeGoods('sales').then(res=>{
        goods.sales.list = res.goods.data
        })
      getHomeGoods('recommend').then(res=>{
        goods.recommend.list = res.goods.data
      })
      getHomeGoods('new').then(res=>{
        goods.new.list = res.goods.data
      })
      console.log(goods)
    })

    const tabClick = (index)=>{
      temid.value = index
    }
    return{
      recommends,
      temid,
      tabClick,
      goods
    }
  },
  components: {
    NavBar,
    RecommendView,
    TabControl,
    GoodsList

  }
}

获得了数据:

截屏2021-07-30 16.45.14.png

GoodsListItem.vue >>

<template>
  <div class="good-item">
    <img :src="product.cover_url" alt="">
    <div class="goods-info">
      <p>{{product.title}}</p>
      <span class="price"><small></small>{{product.price}}</span>
      <span class="collect">{{product.collects_count}}</span>
    </div>

  </div>
</template>

<script >
  export default {
    name: "GoodsListItem",
    props:{
      product:Object,
      default(){
        return{}
      }
    }
  }
</script>

<style scoped lang="scss">
  .good-item{
    width: 45%;
    padding-bottom: 40px;
    position: relative;

    img{
      width: 100%;
      border-radius: 5%;

    }
    .goods-info{
      font-size: 12px;
      /*居中*/
      position: absolute;
      bottom: 5px;
      left: 0;
      right: 0;
      overflow: auto;
      text-align: center;


      p{
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
        margin-bottom: 3px;

      }
      .price{
        color: darkred;
        margin-right: 20px;

      }
      .collect{
        position: relative;

      }
      .collect::before{
        center:'';
        position: absolute;
        left: -15px;
        width: 14px;
        height: 14px;
        top:-1;
        background:url('~assets/images/collect.png')0 0 /14px 14px;
      }

    }
  }

  </style>

GoodsList.vue >>

<template>
  <div class="goods">
    <goods-list-item v-for="item in goods" :product='item' :key="item.id"></goods-list-item>


  </div>
</template>

<script>
  import GoodsListItem from "./GoodsListItem";
  export default {
    name: "GoodsList",
    props:{
      goods:{
        type:Array,
        default(){
          return []
        }
      }
    },
    components:{
      GoodsListItem
    }

  }
</script>

<style scoped>
  .goods{
    display: flex;
    flex-wrap: wrap;
    justify-content: space-around;
  /*  四周环绕*/
    padding: 5px;
  }

</style>

Home.vue >>

<template>
  <div>
    <nav-bar>
      <template v-slot:default>首页</template>
    </nav-bar>

    <div class="banners">
      <img src="~assets/images/img.png" alt="">
    </div>

    <recommend-view :recommends="recommends"></recommend-view>

    <tab-control @tabClick="tabClick" :titles="['畅销','new','精选']"></tab-control>

    <goods-list :goods="showGoods"></goods-list>
    <!--    {{temid}}-->

  </div>
</template>

<script>
  import NavBar from "../../components/common/navbar/NavBar";
  import RecommendView from "./ChildComps/RecommendView";
  import TabControl from "../../components/content/tabControl/TabControl";
  import GoodsList from "../../components/content/goods/GoodsList";
  import {getHomeAllData, getHomeGoods} from "../../network/home";
  import {ref, reactive, onMounted, computed} from 'vue';

  export default {
    name: 'Home',
    setup() {

      const recommends = ref([])

      //商品列表数据模型
      const goods = reactive({
        sales: {page: 0, list: []},
        recommend: {page: 0, list: []},
        new: {page: 0, list: []},

      })

      let currentType = ref('sales')

      const showGoods = computed(() => {
        return goods[currentType.value].list
      })

      onMounted(() => {

        getHomeAllData().then(res => {
          // console.log(res.goods)
          recommends.value = res.goods.data
        })

        getHomeGoods('sales').then(res => {
          goods.sales.list = res.goods.data
        })
        getHomeGoods('recommend').then(res => {
          goods.recommend.list = res.goods.data
        })
        getHomeGoods('new').then(res => {
          goods.new.list = res.goods.data
        })
        console.log(goods)
      })

      const tabClick = (index) => {

        // 切换 '畅销','new','精选'
        let types = ['sales', 'new', 'recommend']
        currentType.value = types[index]
      }
      return {
        recommends,
        // temid,
        tabClick,
        goods,
        showGoods
      }
    },
    components: {
      NavBar,
      RecommendView,
      TabControl,
      GoodsList

    }
  }
</script>
<style scoped>
  .banners img {
    width: 100%;
    height: auto;
    margin-top: 45px;
  }

</style>

7 上拉数据加载更多数据

安装 better-scroll

cnpm i better-scroll -S       

better-scroll指南

8 回到顶部组件

截屏2021-08-07 08.23.14.png Home.vue>>

<template>
  <!-- 2 添加 id="home" -->
  <div id="home">
    <!-- 1-3 引入组件-->
    <nav-bar>
      <template v-slot:default>图书商城</template>
    </nav-bar>
    <!--  11  上拉页面时,固定顶部 -->
    <tab-control
      v-show="isTabFixed"
      @tabClick="tabClick"
      :titles="['畅销', '新书', '精选']"
    />

    <!--  1、开发上拉加载更多数据  固定 class="wrapper" class="content" -->
    <div class="wrapper">
      <div class="content">
        <!--  12 添加外层 div 和 ref  -->
        <div ref="banref">
          <!-- ⚪️ 3-3 轮播图组件: 把数据传给子组件 :banners="banners" -->
          <home-swiper :banners="banners"></home-swiper>

          <!-- 加入推荐组件;-->
          <!-- 6、给子组件传数据:以变量的方式 :recommend="recommend"   -->
          <recommend-view :recommends="recommends"/>
        </div>

        <!-- 3、使用内容选项卡组件, 并定义数组与值传给子组件 -->
        <!--  3 @tabClick 子组件传过来的事件,tabClick 父组件自定义的事件 -->
        <tab-control @tabClick="tabClick" :titles="['畅销', '新书', '精选']"/>
        <!--  6 输出商品列表数据 -->
        <!-- 8、把当前类型的数据,用属性方式传给子组件,默认是销量 -->
        <goods-list :goods="showGoods"/>
      </div>
    </div>

    <!-- 2-3 回到顶部组件;接收子组件传递事件,定义自己的方法 -->
    <back-top @bTop="bTop" v-show="isShowBackTop"></back-top>
  </div>
</template>
<script>


  import NavBar from "../../components/common/navbar/NavBar";// 1-1 引入顶部导航组件
  import RecommendView from "./ChildComps/RecommendView";// 引入推荐组件
  import TabControl from "../../components/content/tabControl/TabControl";//  1 引入内容选项卡组件
  import GoodsList from "../../components/content/goods/GoodsList";
  import BackTop from "../../components/common/backtop/BackTop";// ️ 2-1 回到顶部
  import {getHomeAllData, getHomeGoods} from "../../network/home";//️ 1、 调用 api方法 getHomeGoods
  // ref 空数组; reactive 空对象;
  import {ref, reactive, onMounted, computed, watchEffect, nextTick} from "vue"; //  8 watchEffect 监听所有数据;nextTick 当 DOM渲染完执行的方法 ; 2、引入 reactive 引用代理对象,ref 数组,computed 计算属性、onMounted 生命周期
  import BScroll from "better-scroll"; //  4 引入上拉加载数据插件
  import HomeSwiper from "./ChildComps/HomeSwiper";




  export default {
    name: "Home",

    setup() {
      let isTabFixed = ref(false); // 默认不显示
      let isShowBackTop = ref(false); // ️ 2-4 回到顶部按钮
      let banref = ref(null); //  13
      let banners = ref([]); // ⚪️ 3-4 轮播图

      // let temid = ref(0) // 4 声明临时变量
      // ref 空数组 引用
      const recommends = ref([]); // 3、声明数据引用对象为空数组

      // const demo = reactive({}) // reactive 空对象 代理
      // 商品列表数据模型
      const goods = reactive({
        // 3
        sales: {page: 1, list: []},
        new: {page: 1, list: []},
        recommend: {page: 1, list: []},
      });

      // 6、当前类型数据
      let currentType = ref("sales");

      const showGoods = computed(() => {
        // computed 计算属性
        return goods[currentType.value].list;
      });

      let bscroll = reactive({}); //  5 声明在外层 共用

      onMounted(() => {
        getHomeAllData().then((res) => {
          recommends.value = res.goods.data; // 4、调用api方法,把获取到的数据赋值给定义的空数组
          banners.value = res.slides; // ⚪️ 3-6 获取轮播图数据
          // console.log(res.slides)
        });
        // 5、按销量查询
        getHomeGoods("sales").then((res) => {
          goods.sales.list = res.goods.data;
        });
        // 5、按最新查询
        getHomeGoods("new").then((res) => {
          goods.new.list = res.goods.data;
        });
        // 5、按推荐查询
        getHomeGoods("recommend").then((res) => {
          goods.recommend.list = res.goods.data;
        });
        // console.log(goods)

        //  6 创建 BetterScroll 对象
        bscroll = new BScroll(document.querySelector(".wrapper"), {
          // 获取到最外层元素
          probeType: 3, // 0,1,2,3, 3 只要在运行就触发 scroll 事件
          click: true, // 是否允许点击
          pullUpLoad: true, // 上拉加载更多,默认 false
        });
        //  7 触发滚动事件 - scroll 事件 - 位置 position
        bscroll.on("scroll", (position) => {
          // console.log(banref.value.offsetHeight) // 15 offsetHeight 偏移量的高度
          // console.log(-position.y) // 打印滚动的距离
          isShowBackTop.value = isTabFixed.value = -position.y > banref.value.offsetHeight; // ️ 2-6 isShowBackTop.value =
        });

        // 10 上拉加载更多数据,触发 pullingUp
        bscroll.on("pullingUp", () => {
          console.log('上拉加载更多......')
          console.log('centerHeight:' + document.querySelector('.content').clientHeight)
          const page = goods[currentType.value].page + 1;
          getHomeGoods(currentType.value, page).then((res) => {
            goods[currentType.value].list.push(...res.goods.data);
            goods[currentType.value].page += 1;
          });
          // 完成上拉,等数据请求完成,要将新数据展示出来
          bscroll.refresh(); // 刷新 重新计算高度

          bscroll.finishPullUp();
          console.log('当前类型:' + currentType.value + ',当前页:' + page)
        });
      });

      const tabClick = (index) => {
        //  5
        // temid.value = index // 等于点击选项卡传过来的 索引
        let types = ["sales", "new", "recommend"];
        currentType.value = types[index];
        nextTick(() => {
          // 当 DOM 渲染完了执行方法
          // 重新计算高度
          bscroll && bscroll.refresh();
        });
      };

      //  9 监听 任何一个变量有变化就会被触发
      watchEffect(() => {
        nextTick(() => {
          // 当 DOM 渲染完了执行方法
          // 重新计算高度
          bscroll && bscroll.refresh();
        });
      });

      const bTop = () => {
        // ️ 2-7 回到顶部方法
        // console.log('1111111111')
        bscroll.scrollTo(0, 0, 500); // 前两个参数是x,y的位置,延迟 500 毫秒回到顶部
      };

      return {
        recommends, // 5、 返回数据
        // temid, // 6 临时的,现在不用了
        tabClick, //  7
        goods, // 4
        showGoods, // 7
        isTabFixed,
        banref, // 14
        isShowBackTop, // ️ 2-5 回到顶部
        bTop, //  2-8
        banners, // ⚪️ 3-5 轮播图数据
      };
    },

    components: {
      NavBar, // 1-2 注册组件
      RecommendView,
      TabControl, // 2、注册内容选项卡组件
      GoodsList, //  5
      BackTop, // ️ 2-2 注册回到顶部组件
      HomeSwiper, // ⚪️ 3-2 注册轮播图组件
    },
  };
</script>
<style lang="scss" scoped>
  #home {
    //  3 这一整块 固定高度
    height: 100vh; // 占领整个屏幕区域高度
    position: relative;

    .wrapper {
      position: absolute;
      top: 45px;
      bottom: 50px;
      left: 0; // 左右为0 内容可居中
      right: 0;
      overflow: hidden; // 超出部分隐藏
    }
  }
</style>

9 keep-alife(最好最后写!!)

路由跳转后(切换界面后),用keep-alife 组件保持操作之前的记忆。 注: 项目完成后在加上

App.vue >>

<route-view v-slot="{Component}">
 <transition>
   <keep-alive>
     <component :is="Component" />
   </keep-alive>
 </transition>
</route-view>

10.vint组件swiper

1.安装vint组件

Vue 3 项目,安装 Vant 3:

cnpm i vant@next -S

2.安装插件babel-plugin-import 

是一款 babel 插件,它会在编译过程中将 import 的写法自动转换为按需引入的方式。

cnpm i babel-plugin-import -D
  1. babel.config.js >>
module.exports = {
  presets: [
    '@vue/cli-plugin-babel/preset'
  ],
  plugins: [
    ['import', {
      libraryName: 'vant',
      libraryDirectory: 'es',
      style: true
    }, 'vant']
  ]
}

4.main.js >>

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import 'vant/lib/index.css'

import { Swipe, SwipeItem } from 'vant'

createApp(App)
  .use(Swipe).use(SwipeItem)
  .use(store).use(router).mount('#app')

5.Home.vue >>

<template>
  <!-- 2 添加 id="home" -->
  <div id="home">
    <!-- 1-3 引入组件-->
    <nav-bar>
      <template v-slot:default>首页</template>
    </nav-bar>
    <!--  11  上拉页面时,固定顶部 -->
    <tab-control v-show="isTabFixed" @tabClick="tabClick" :titles="['畅销','new','精选']"></tab-control>

    <!--  1、开发上拉加载更多数据  固定 class="wrapper" class="content" -->
    <div class="wrapper">
      <div class="content">
        <!--  12 添加外层 div 和 ref  -->
        <div ref="banref">
          <div class="banners">
            <home-swiper style="margin-top:45px" :banners="banners"></home-swiper>

          </div>

          <!-- 加入推荐组件;-->
          <!-- 6、给子组件传数据:以变量的方式 :recommend="recommend"   -->
          <recommend-view :recommends="recommends"/>
        </div>

        <!-- 3、使用内容选项卡组件, 并定义数组与值传给子组件 -->
        <!--  3 @tabClick 子组件传过来的事件,tabClick 父组件自定义的事件 -->
        <tab-control @tabClick="tabClick" :titles="['畅销','new','精选']"></tab-control>
        <!--  6 输出商品列表数据 -->
        <!-- 8、把当前类型的数据,用属性方式传给子组件,默认是销量 -->
        <goods-list :goods="showGoods"/>
      </div>
    </div>

    <!-- 2-3 回到顶部组件;接收子组件传递事件,定义自己的方法 -->
    <back-top @bTop="bTop" v-show="isShowBackTop"/>
  </div>
</template>

<script>
  import NavBar from "../../components/common/navbar/NavBar";// 1-1 引入顶部导航组件
  import RecommendView from "./ChildComps/RecommendView";// 引入推荐组件
  import TabControl from "../../components/content/tabControl/TabControl";//  1 引入内容选项卡组件
  import GoodsList from "../../components/content/goods/GoodsList";
  import BackTop from "../../components/common/backtop/BackTop";// ️ 2-1 回到顶部
  import {getHomeAllData, getHomeGoods} from "../../network/home";//️ 1、 调用 api方法 getHomeGoods
  // ref 空数组; reactive 空对象;
  import {ref, reactive, onMounted, computed,watchEffect,nextTick} from 'vue';
  import BScroll from "better-scroll"; //  4 引入上拉加载数据插件
  import HomeSwiper from "./ChildComps/HomeSwiper";

  export default {
    name: 'Home',
    setup() {

      let isShowBackTop = ref(false) // ️ 2-4 回到顶部按钮
      let banref = ref(null) //  13
      let isTabFixed = ref(false)// 默认不显示
      const recommends = ref([])// 3、声明数据引用对象为空数组
      // const demo = reactive({}) // reactive 空对象 代理
      let banners = ref([])

      //商品列表数据模型
      const goods = reactive({
        //3
        sales: {page: 1, list:[]},
        new: {page: 1, list:[]},
        recommend: {page: 1, list:[]},
      })

      // 6、当前类型数据
      let currentType = ref('sales')

      // computed 计算属性
      const showGoods = computed(() => {
        return goods[currentType.value].list
      })

      let bscroll = reactive({})// 5 声明在外层 共用

      onMounted(() => {

        getHomeAllData().then(res => {
          recommends.value = res.goods.data//4、调用api方法,把获取到的数据赋值给定义的空数组
          banners.value = res.slides

        })

        // 5、按销量查询
        getHomeGoods('sales').then(res => {
          goods.sales.list = res.goods.data
        })
        // 5、按推荐查询
        getHomeGoods('recommend').then(res => {
          goods.recommend.list = res.goods.data
        })
        // 5、按最新查询
        getHomeGoods('new').then(res => {
          goods.new.list = res.goods.data
        })
        // console.log(goods)

        //6 创建BetterScroll 对象
        bscroll = new BScroll(document.querySelector('.wrapper'),{
          // 获取到最外层元素
          probeType:3,//0,1,2,3   3只要运动就触发scroll事件
          click:true,//是否运行点击
          pullUpLoad:true//上拉加载更多,默认是false
        })

        //7触发滚动事件- scroll 事件 - 位置 position
        bscroll.on('scroll',(postion)=>{
          console.log(banref.value.offsetHeight)// 15 offsetHeight 偏移量的高度
          console.log(-position.y)// 打印滚动的距离

          isShowBackTop.value = isTabFixed.value = (-position.y) > banref.value.offsetHeight
        })
        //上拉加载更多
        bscroll.on('pullingUp',()=>{
          console.log('上拉加载更多...')

          const page = goods[currentType.value].page+1

          getHomeGoods(currentType.value,page).then(res=>{
            goods[currentType.value].list.push(...res.goods.data)
            goods[currentType.value].page += 1
          })
          //完成上拉,等数据请求加载完,得将数据展示出来
          bscroll.finishPullUp()

          //重新计算高度
          bscroll.refresh()
          console.log('content-height'+document.querySelector('.content').clientHeight)
          console.log('当前类型'+ currentType.value +',当前页'+page)

        })
      })



      const tabClick = (index) => {

        // 切换 '畅销','new','精选'
        let types = ['sales', 'new', 'recommend']
        currentType.value = types[index]

        nextTick(()=>{
          //重新计算高度
          bscroll && bscroll.refresh()
        })
      }

      //9监听 任何一个变量有变化
      watchEffect(()=>{
        nextTick(()=>{
          // 当 DOM 渲染完了执行方法
          //重新计算高度
          bscroll && bscroll.refresh()
        })
      })

      //回到顶部按钮
      const bTop = () =>{
        // ️ 2-7 回到顶部方法
        bscroll.scrollTo(0,0,500)// 前两个参数是x,y的位置,延迟 500 毫秒回到顶部
      }

      return {
        recommends,// 5、 返回数据
        // temid, // 6 临时的,现在不用了
        tabClick,//7
        goods,//4
        showGoods,//7
        isShowBackTop,// ️ 2-5 回到顶部
        isTabFixed,
        banref,//14
        bTop,//  2-8
        banners

      }
    },
    components: {
      NavBar,// 1-2 注册组件
      RecommendView,
      TabControl,// 2、注册内容选项卡组件
      GoodsList,//  5
      BackTop,// ️ 2-2 注册回到顶部组件
      HomeSwiper

    },

  }


</script>
<style scoped>
  .banners img {
    width: 100%;
    height: auto;
  }
  #home{
  /*  3 这一整块 固定高度*/
  height: 100vh;/*占领整个屏幕区域高度*/
    position: relative;
  }
  .wrapper{
    position: absolute;
    top: 45px;
    bottom: 50px;
    left: 0;/*左右为0 内容可居中*/
    right: 0;
    overflow: hidden;/*超出部分隐藏*/
  }
  .content{

  }

</style>
  1. HomeSwiper.vue >>
<template>
  <van-swipe v-if="banners.length" class="my-swipe" :autoplay="3000" indicator-color="white">
    <van-swipe-item v-for="(item,index) in banners" :key="index">
      <img :src="item.img_url" alt="">
    </van-swipe-item>

  </van-swipe>

</template>

<script>
  export default {
    name: "HomeSwiper",
    props:{
      banners:Array,
      default(){
        return[]
      }
    }
  }
</script>

<style scoped>
.img{
  width: 100%;
  height: auto !important;
}
</style>

11.Vant组件库图片懒加载和徽章

1.main.js >>

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import 'vant/lib/index.css'

import { Swipe, SwipeItem, Lazyload,Badge} from 'vant'

createApp(App)
  .use(Swipe).use(SwipeItem).use(Lazyload,{
    loading:require('./assets/images/default.png')
  }).use(Badge)
  .use(store).use(router).mount('#app')

将 v-lazy 指令的值设置为你需要懒加载的图片。

<img v-for="img in imageList" v-lazy="img" />

徽标

<router-link class="tab-bar-item" to="/shopcart">
  <div class="icon">
    <van-badge :content="0" max="9">
      <img src="~assets/images/购物车.png" height="20" width="20"/>
    </van-badge>
  </div>
  <div>购物车</div>
</router-link>

3 项目 - 分类页面

1 分类页面布局和菜单

截屏2021-08-07 22.12.18.png

Sidebar 侧边导航

引入

import Vue from 'vue';
import { Sidebar, SidebarItem } from 'vant';

Vue.use(Sidebar);
Vue.use(SidebarItem);

Collapse 折叠面板, 手风琴

引入

import Vue from 'vue';
import { Collapse, CollapseItem } from 'vant';

Vue.use(Collapse);
Vue.use(CollapseItem);

通过 accordion 可以设置为手风琴模式,最多展开一个面板,此时 activeName 为字符串格式。

<van-collapse v-model="activeName" accordion>
  <van-collapse-item title="标题1" name="1">内容</van-collapse-item>
  <van-collapse-item title="标题2" name="2">内容</van-collapse-item>
  <van-collapse-item title="标题3" name="3">内容</van-collapse-item>
</van-collapse>

注册

main.js>>

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import 'vant/lib/index.css'

import { Swipe, SwipeItem, Lazyload,Badge,Sidebar, SidebarItem,Collapse, CollapseItem} from 'vant'

createApp(App)
  .use(Swipe).use(SwipeItem).use(Lazyload,{
    loading:require('./assets/images/default.png')
  }).use(Badge).use(Sidebar).use(SidebarItem).use(Collapse).use(CollapseItem)
  .use(store).use(router).mount('#app')

Category.js>>

import {request} from "./request";

export function getCategory() {
  return request({
    url:'/api/goods',

  })
}

Category.vue>>

<template>
  <div>
    <nav-bar>
      <template v-slot:default>分类详情</template>
    </nav-bar>
    <div id="mainbox">
      <div class="ordertab">

      </div>
<!--折叠-->
      <van-sidebar class="leftmenu" v-model="activeKey">
        <van-collapse v-model="activeName" accordion><!--手风琴折叠-->
          <van-collapse-item
            v-for="item in categories" :key="item.id"
            :title="item.name"
            :name="item.name">

<!--          4.变量categories.value中数据-->
          <van-sidebar-item
            v-for="sub in item.children"
            :title="sub.name" />

          </van-collapse-item>
        </van-collapse>
      </van-sidebar>

      <div class="goodslist">

      </div>
    </div>
  </div>
</template>

<script>
  import NavBar from "../../components/common/navbar/NavBar";
  import {ref,reactive,onMounted} from 'vue';
  import {getCategory} from "../../network/category";

  export default {
    name: "Category",
    setup(){
      let activeKay = ref(0)
      let activeName = ref(1)
      //2.申明数组
      let categories = ref([])

      onMounted(()=>{
        //1 获取分类
        getCategory().then(res=>{
          // console.log(res)
          // 3.获取res.categories的值,并赋给categories.value
          categories.value  = res.categories
          console.log(categories.value)
        })
      })
      return{
        activeKay,
        categories,
        activeName
      }
    },
    components: {
      NavBar
    }
  }
</script>

<style scoped lang="scss">
  #mainbox{
    margin-top: 45px;
    display: flex;

    .ordertab{
      width: 100%;
      height: 50px;
      flex: 1;
      float: right;
      z-index: 9;
      position: fixed;
      top: 45px;
      right: 0;
      left: 130px;

    }
    .leftmenu{
      width: 130px;
      position: fixed;
      top: 95px;
      left: 0;
      width: 130px;
    }
    .goodslist{
      flex: 1;
      position: absolute;
      top: 100px;
      left: 130px;
      right: 0;
      height: 100vh; /*  占领整个可用大小*/

    }
  }

</style>

2 分类页面的交互模式(下拉加载更多)

截屏2021-08-07 22.39.29.png

Card 卡片

引入

import Vue from 'vue';
import { Card } from 'vant';

Vue.use(Card);

营销信息

通过 origin-price 设置商品原价,通过 tag 设置商品左上角标签。

<van-card
  num="2"
  tag="标签"
  price="2.00"
  desc="描述信息"
  title="商品标题"
  thumb="https://img01.yzcdn.cn/vant/ipad.jpeg"
  origin-price="10.00"
/>

Category.vue>>

<template>
  <div>
    <nav-bar>
      <template v-slot:default>分类详情</template>
    </nav-bar>
    <div id="mainbox">
      <!--6 不同排序方式-->
      <div class="ordertab">
        <van-tabs v-model="active" @click="tabClick">
          <van-tab title="销量排序"></van-tab>
          <van-tab title="价格排序"></van-tab>
          <van-tab title="评价排序"></van-tab>
        </van-tabs>

      </div>
      <!--5 折叠-->
      <van-sidebar class="leftmenu" v-model="activeKey">
        <van-collapse v-model="activeName" accordion><!--手风琴折叠-->
          <van-collapse-item
            v-for="item in categories" :key="item.id"
            :title="item.name"
            :name="item.name">

<!--            子菜单-->
          <van-sidebar-item
            v-for="sub in item.children"
            :title="sub.name"
            :key="sub.id"
            @click="getGoods(sub.id)"
          /><!--    4.变量categories.value中数据-->

          </van-collapse-item>
        </van-collapse>
      </van-sidebar>
<!--商品列表-->
      <div class="goodslist">
        <div class="content">
          <van-card
            num="2"
            tag="标签"
            price="2.00"
            desc="描述信息"
            title="商品标题"
            thumb="https://img01.yzcdn.cn/vant/ipad.jpeg"
            origin-price="10.00"
          />



        </div>



      </div>
    </div>
  </div>
</template>

<script>
  import NavBar from "../../components/common/navbar/NavBar";
  import {ref,reactive,onMounted} from 'vue';
  import {getCategory} from "../../network/category";

  export default {
    name: "Category",
    setup(){
      let active = ref(1)
      let activeKay = ref(0)
      let activeName = ref(1)
      //2.申明数组
      let categories = ref([])

      //8当前的排序条件
      let currentOrder = ref(['sales'])
      //11 当前的分类id
      let currentCid = ref(0)


      onMounted(()=>{
        //1 获取分类
        getCategory().then(res=>{
          // console.log(res)
          // 3.获取res.categories的值,并赋给categories.value
          categories.value  = res.categories
          console.log(categories.value)
        })
      })

      //7 排序选项卡
      const tabClick = (index)=>{
        let orders = ['sales','price','comment_count']

        //9
        currentOrder.value = orders[index]
        console.log('排序的序号'+ orders[index])
        console.log(currentCid.value)

      }
      //10 通过分类得到商品
      const getGoods = (cid) =>{
        currentCid.value = cid
        console.log('当前分类Id'+currentCid.value)
        console.log('排序的序号'+ currentCid.value)

      }
      return{
        activeKay,
        categories,
        activeName,
        active,
        tabClick,
        getGoods,
        currentCid,

      }
    },
    components: {
      NavBar
    }
  }
</script>

<style scoped lang="scss">
  #mainbox{
    margin-top: 45px;
    display: flex;

    .ordertab{
      height: 50px;
      flex: 1;
      float: right;
      z-index: 9;
      position: fixed;
      top: 45px;
      right: 0;
      left: 130px;

    }
    .leftmenu{
      width: 130px;
      position: fixed;
      top: 95px;
      left: 0;
      width: 130px;
    }
    .goodslist{
      flex: 1;
      position: absolute;
      top: 100px;
      left: 130px;
      right: 0;
      height: 100vh; /*  占领整个可用大小*/
      padding: 10px;

    }
  }

</style>

3 从接口中获取分类数据

截屏2021-08-10 10.13.00.png

category.vue >>

<template>
  <div>
    <nav-bar>
      <template v-slot:default>分类详情</template>
    </nav-bar>
    <div id="mainbox">
      <!--6 不同排序方式-->
      <div class="ordertab">
        <van-tabs v-model="active" @click="tabClick">
          <van-tab title="销量排序"></van-tab>
          <van-tab title="价格排序"></van-tab>
          <van-tab title="评价排序"></van-tab>
        </van-tabs>

      </div>
      <!--5 折叠-->
      <van-sidebar class="leftmenu" v-model="activeKey">
        <van-collapse v-model="activeName" accordion><!--手风琴折叠-->
          <van-collapse-item
            v-for="item in categories" :key="item.id"
            :title="item.name"
            :name="item.name">

<!--            子菜单-->
          <van-sidebar-item
            v-for="sub in item.children"
            :title="sub.name"
            :key="sub.id"
            @click="getGoods(sub.id)"
          /><!--    4.变量categories.value中数据-->

          </van-collapse-item>
        </van-collapse>
      </van-sidebar>
<!--商品列表-->
      <div class="goodslist">
        <div class="content">
          <van-card
            v-for="item in showGoods" :key="item.id"
            :num="item.comments_count"
            :tag="item.comments_count >=0 ? '流行':'标签'"
            :price="item.price"
            :desc="item.update_at"
            :title="item.title"
            :thumb="item.cover_url"
            :lazy-load="true"
          />

        </div>
      </div>
    </div>
  </div>
</template>

<script>
  import NavBar from "../../components/common/navbar/NavBar";
  import {ref,reactive,onMounted,computed} from 'vue';
  import {getCategory,getCategoryGoods} from "../../network/category";

  export default {
    name: "Category",
    setup(){
      let active = ref(1)
      let activeKay = ref(0)
      let activeName = ref(1)
      //2.申明数组
      let categories = ref([])

      //8当前的排序条件
      let currentOrder = ref(['sales'])
      //11 当前的分类id
      let currentCid = ref(0)

      //12数据模型
      const goods = reactive({
        sales:{page:1,list:[]},
        price:{page:1,list:[]},
        comments_count:{page:1,list:[]}
      })
      //13响应式的,更新数据
      const showGoods = computed(()=>{
        return goods[currentOrder.value].list
      })

      //13 初始化数据,将接口里的数据放到数据模型中去
      const init =()=> {
        getCategoryGoods('sales',currentCid.value).then(res=>{
          goods.sales.list = res.goods.data
        })
        getCategoryGoods('price',currentCid.value).then(res=>{
          goods.price.list = res.goods.data
        })
        getCategoryGoods('comments_count',currentCid.value).then(res=>{
          goods.comments_count.list = res.goods.data
        })
      }

      onMounted(()=>{
        //1 获取分类
        getCategory().then(res=>{
          // console.log(res)
          // 3.获取res.categories的值,并赋给categories.value
          categories.value  = res.categories
          console.log(categories.value)
        })
        //只需一个,因为默认就是「sales」
        getCategoryGoods('sales',currentCid.value).then(res=>{
          goods.sales.list = res.goods.data
        })
      })

      //7 排序选项卡
      const tabClick = (index)=>{
        let orders = ['sales','price','comment_count']

        //9
        currentOrder.value = orders[index]
        //14 更新,重新排序
        getCategoryGoods(currentOrder.value ,currentCid.value).then(res=>{
          goods[currentOrder.value].list = res.goods.data
        })
        console.log('当前分类id'+ currentCid.value)
        console.log('排序的序号'+currentOrder.value)

      }
      //10 通过分类得到商品
      const getGoods = (cid) =>{
        currentCid.value = cid
        init()//重新赋值

        console.log('当前分类Id'+currentCid.value)
        console.log('排序的序号'+ currentCid.value)

      }
      return{
        activeKay,
        categories,
        activeName,
        active,
        tabClick,
        getGoods,
        currentCid,
        goods,
        showGoods

      }
    },
    components: {
      NavBar
    }
  }
</script>

<style scoped lang="scss">
  #mainbox{
    margin-top: 45px;
    display: flex;

    .ordertab{
      height: 50px;
      flex: 1;
      float: right;
      z-index: 9;
      position: fixed;
      top: 45px;
      right: 0;
      left: 130px;

    }
    .leftmenu{
      width: 130px;
      position: fixed;
      top: 95px;
      left: 0;
      width: 130px;
    }
    .goodslist{
      flex: 1;
      position: absolute;
      top: 100px;
      left: 130px;
      right: 0;
      height: 100vh; /*  占领整个可用大小*/
      padding: 10px;
      text-align: left !important;
    }
  }

  .van-card__thumb{
    width: 68px !important;
  }

</style>

4 better_scroll 上拉加载更多数据

category.vue>>

<template>
  <div>
    <nav-bar>
      <template v-slot:default>分类详情</template>
    </nav-bar>
    <div id="mainbox">
      <!--6 不同排序方式-->
      <div class="ordertab">
        <van-tabs v-model="active" @click="tabClick">
          <van-tab title="销量排序"></van-tab>
          <van-tab title="价格排序"></van-tab>
          <van-tab title="评价排序"></van-tab>
        </van-tabs>

      </div>
      <!--5 折叠-->
      <van-sidebar class="leftmenu" v-model="activeKey">
        <van-collapse v-model="activeName" accordion><!--手风琴折叠-->
          <van-collapse-item
            v-for="item in categories" :key="item.id"
            :title="item.name"
            :name="item.name">

<!--            子菜单-->
          <van-sidebar-item
            v-for="sub in item.children"
            :title="sub.name"
            :key="sub.id"
            @click="getGoods(sub.id)"
          /><!--    4.变量categories.value中数据-->

          </van-collapse-item>
        </van-collapse>
      </van-sidebar>
<!--商品列表-->
      <div class="goodslist">
        <div class="content">
          <van-card
            v-for="item in showGoods" :key="item.id"
            :num="item.comments_count"
            :tag="item.comments_count >=0 ? '流行':'标签'"
            :price="item.price"
            :desc="item.update_at"
            :title="item.title"
            :thumb="item.cover_url"
            :lazy-load="true"
          />

        </div>
      </div>
    </div>

    <back-top @bTop="bTop" v-show="isShowBackTop"></back-top>

  </div>
</template>

<script>
  import NavBar from "../../components/common/navbar/NavBar";
  import BackTop from "../../components/common/backtop/BackTop";// ️ 2-1 回到顶部

  import {ref, reactive, onMounted, computed, watchEffect, nextTick} from 'vue';
  import {getCategory,getCategoryGoods} from "../../network/category";
  import BScroll from "better-scroll";


  export default {
    name: "Category",
    setup(){
      let active = ref(1)
      let activeKay = ref(0)
      let activeName = ref(1)
      //2.申明数组
      let categories = ref([])

      let isShowBackTop =ref(false)

      //8当前的排序条件
      let currentOrder = ref(['sales'])
      //11 当前的分类id
      let currentCid = ref(0)

      //12数据模型
      const goods = reactive({
        sales:{page:1,list:[]},
        price:{page:1,list:[]},
        comments_count:{page:1,list:[]}
      })
      //13响应式的,更新数据
      const showGoods = computed(()=>{
        return goods[currentOrder.value].list
      })

      //13 初始化数据,将接口里的数据放到数据模型中去
      const init =()=> {
        getCategoryGoods('sales',currentCid.value).then(res=>{
          goods.sales.list = res.goods.data
        })
        getCategoryGoods('price',currentCid.value).then(res=>{
          goods.price.list = res.goods.data
        })
        getCategoryGoods('comments_count',currentCid.value).then(res=>{
          goods.comments_count.list = res.goods.data
        })
      }

      let bscroll = reactive({}); //   声明在外层 共用

      onMounted(()=>{
        //1 获取分类
        getCategory().then(res=>{
          // console.log(res)
          // 3.获取res.categories的值,并赋给categories.value
          categories.value  = res.categories
          // console.log(categories.value)
        })
        //只需一个,因为默认就是「sales」
        getCategoryGoods('sales',currentCid.value).then(res=>{
          goods.sales.list = res.goods.data
        })

        //  6 创建 BetterScroll 对象
        bscroll = new BScroll(document.querySelector(".goodslist"), {
          // 获取到最外层元素
          probeType: 3, // 0,1,2,3, 3 只要在运行就触发 scroll 事件
          click: true, // 是否允许点击
          pullUpLoad: true, // 上拉加载更多,默认 false
        });
        //注册滚动事件
        bscroll.on('scroll',(position)=>{
         isShowBackTop.value = (-position.y)>30
        })
        // 10 上拉加载更多数据,触发 pullingUp
        bscroll.on("pullingUp", () => {
          console.log('上拉加载更多......')

          const pages = goods[currentOrder.value].page +1

          getCategoryGoods(currentOrder.value ,currentCid.value).then(res=>{
            goods[currentOrder.value].list.push(...res.goods.data)
            goods[currentOrder.value].page += 1

          })


          // 完成上拉,等数据请求完成,要将新数据展示出来
          bscroll.finishPullUp();

          //延迟效果
          nextTick(() => {
            // 当 DOM 渲染完了执行方法
            // 重新计算高度
            bscroll && bscroll.refresh();
          });

          // 刷新 重新计算高度
          bscroll.refresh();
          console.log('centerHeight:' + document.querySelector('.content').clientHeight)
          console.log('当前类型:' + currentType.value + ',当前页:' + page)
        });
      });


      //7 排序选项卡
      const tabClick = (index)=>{
        let orders = ['sales','price','comment_count']

        //9
        currentOrder.value = orders[index]
        //14 更新,重新排序
        getCategoryGoods(currentOrder.value ,currentCid.value).then(res=>{
          goods[currentOrder.value].list = res.goods.data

          //延迟效果
          nextTick(() => {
            // 当 DOM 渲染完了执行方法
            // 重新计算高度
            bscroll && bscroll.refresh();
          });
        })

        console.log('当前分类id'+ currentCid.value)
        console.log('排序的序号'+currentOrder.value)

      }
      //10 通过分类得到商品
      const getGoods = (cid) =>{
        currentCid.value = cid
        init()//重新赋值

        console.log('当前分类Id'+currentCid.value)
        console.log('排序的序号'+ currentCid.value)

      }

      //   监听 任何一个变量有变化就会被触发
      watchEffect(() => {
        nextTick(() => {
          // 当 DOM 渲染完了执行方法
          // 重新计算高度
          bscroll && bscroll.refresh();
        });
      });

      const bTop =()=>{
        bscroll.scrollTo(0,0,300)
      }

      return{
        activeKay,
        categories,
        activeName,
        active,
        tabClick,
        getGoods,
        currentCid,
        goods,
        showGoods,
        bscroll,
        isShowBackTop,
        bTop

      }
    },
    components: {
      NavBar,
      BackTop
    }
  }
</script>

<style scoped lang="scss">
  #mainbox{
    margin-top: 45px;
    display: flex;

    .ordertab{
      height: 50px;
      flex: 1;
      float: right;
      z-index: 9;
      position: fixed;
      top: 45px;
      right: 0;
      left: 130px;

    }
    .leftmenu{
      width: 130px;
      position: fixed;
      top: 95px;
      left: 0;
      width: 130px;
    }
    .goodslist{
      flex: 1;
      position: absolute;
      top: 100px;
      left: 130px;
      right: 0;
      height: 100vh; /*  占领整个可用大小*/
      padding: 10px;
      text-align: left !important;
      /*.content{*/
      /*  !*background-color: darkred;*!*/
      /*  padding-top: 10px;*/
      /*}*/
    }
  }

  .van-card__thumb{
    width: 68px !important;
  }

</style>

(这里报错了,下次修改)

截屏2021-08-10 11.06.55.png