vue3小电商平台项目小知识点总结

axios

1.axios的基础封装

随着项目规模增大,如果每发起一次HTTP请求,就要把这些比如设置超时时间、设置请求头、根据项目环境判断使用哪个请求地址、错误处理等等操作,都需要写一遍

这种重复劳动不仅浪费时间,而且让代码变得冗余不堪,难以维护。为了提高我们的代码质量,我们应该在项目中二次封装一下 axios 再使用

如果每个页面都发送类似的请求,都要写一堆的配置与错误处理,就显得过于繁琐了

这时候我们就需要对axios进行二次封装,让使用更为便利

import axios from 'axios'

// 创建axios实例
const http = axios.create({
  baseURL: 'http://localhose'(请求前缀),
  timeout: 5000(超时事件)
})

// axios请求拦截器
instance.interceptors.request.use(config => {
  return config
}, e => Promise.reject(e))

// axios响应式拦截器
instance.interceptors.response.use(res => res.data, e => {
  return Promise.reject(e)
})

//导出
export default http;

案例应用

import http from '@/utils/http'

export function getCategoryAPI () {
  return http({
    url: '/xxx/xxxx'(资源路径)
  })
}

Pinia

1.状态管理优化请求

解决问题:在一些页面组件上发送了相同的资源请求,使用pinia的状态管理来优化为一次请求

  • 创建useCategoryStore
import { getCategoryAPI } from "@/api/layout";
import { ClassData } from "@/api/model/layoutModel";
import { defineStore } from "pinia";
import { ref } from 'vue'

export const useCategoryStore = defineStore('category', () =>{
    //state导航列表数据
    const categoryList = ref([] as ClassData[])
    
    //aciton获取导航数据的方法
    const getCategory = async() => {
        const res = await getCategoryAPI();
        categoryList.value = res.result
    }
    //返回数据
    return {
        categoryList,
        getCategory
    }
})
  • 在页面加载之前使用onMounted通过aciton进行状态计算
import { useCategoryStore } from '@/stores/categoryStore'
import { onMounted } from 'vue'
const categoryStore = useCategoryStore();
//在页面初始化得到category 使用pinia进行状态管理 将两次请求变成一次请求
onMounted(() => {
  categoryStore.getCategory()
})
  • 在组件中使用通过categoryStore中return的state数据
<script setup lang="ts">
import { useCategoryStore } from '@/stores/categoryStore';
const categoryStore = useCategoryStore();
</script>

<template>
	<li class="home" v-for="item in categoryStore.categoryList" :key="item.id">
        <RouterLink to="/">{{ item.name }}</RouterLink>
	</li>
</template>

2.Pinia管理用户数据

基本思想:Pinia负责用户数据相关的state和action,组件中只负责触发action函数并传递参数

// 管理用户数据相关
import { UserInfo } from "@/api/model/loginModel";
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { loginAPI } from '@/apis/user'

export const useUserStore = defineStore('user', () => {
  // 1. 定义管理用户数据的state
  const userInfo = ref({} as UserInfo)
  // 2. 定义获取接口数据的action函数
  const getUserInfo = async ({ account, password }) => {
    const res = await loginAPI({ account, password })
    userInfo.value = res.result
  }
  // 3. 以对象的格式把state和action return
  return {
    getUserInfo
  }
}, {
  //持久化,方便后续使用
  persist: true,
})

3.pinia本地购物车

在用户未登录的状态下,如果对某个商品进行了加入购物车的操作,将会加入到本地购物车

在用户登录的状态下,如果对某个商品进行了加入购物车的操作,将会走后台接口将数据写入数据库,并且将pinia本地购物车中的物品也更新到数据库中
在这里插入图片描述

import { addCartAPI} from "@/api/cart";

export const useCartStore = defineStore('cart',() => {
    //state => cartList;
    const cartList = ref([] as CartItem[]);
    
    const userStore = useUserStore();
    //获取token查看是否登录
    const isLogin = computed(() => userStore.userInfo.token)

    //aciton => addCart
    const addCart = async ( good : CartItem) => {
        if(isLogin){
            //已经登录
            //调用接口
            const {skuId,count} = good;
            //接口api
            await addCartAPI({skuId,count});
            const res = await findNewCartListAPI();
            cartList.value = res.result;
        }else{
            //没有登录
            //添加购物车
            //已经添加过 => count + 1
            //没有添加过 => 直接push
            const item  = cartList.value.find((item) => good.skuId === item.skuId)
            if(item) {
                item.count++;
            }else{
                cartList.value.push(good)
            }
        }
        
    }
    // 获取最新购物车列表
    const updateNewList = async () => {
        const res = await findNewCartListAPI();
        cartList.value = res.result;
    };
}

合并购物车,实现于用户登录的时候

import { mergeCartAPI } from '@/api/cart'

export const useUserStore = defineStore('user' ,() => {

    const cartStore = useCartStore();

    //定义管理数据的state
    const  userInfo = ref({} as UserInfo)
    //定义获取接口函数的action函数
    const getUserInfo = async({ account, password}) =>{
        const res = await loginAPI({ account, password})
        userInfo.value = res.result
        //   合并购物车
      await mergeCartAPI(
        cartStore.cartList.map((item) => {
          return {
            skuId: item.skuId,
            selected: item.selected,
            count: item.count
          };
        })
      );
      //更新购物车的列表
      await cartStore.updateNewList();
    }
}

vue

1.组件封装复用

纯展示类组件通用封装思路总结:

  1. 搭建纯静态的部分,不管可变的部分
  2. 抽象可变的部分为组件参数:非复杂的模板抽象为props,复杂的结构模板抽象为插槽

HomePanel.vue

<script setup lang="ts">
defineProps<{
    title: string;
    subTitle: string;
}>()
</script>


<template>
  <div class="home-panel">
    <div class="container">
      <div class="head">
         <!-- 主标题和副标题 -->
        <h3>
          {{ title }}<small>{{ subTitle }}</small>
        </h3>
      </div>
      <!-- 主体内容区域 -->
      <slot />
    </div>
  </div>
</template>

index.vue

//导入组件
import HomePanel from './components/HomePanel.vue'

<template>
    <HomePanel title="新鲜好物" sub-title="新鲜好物 好多商品"> 
        <div>我是新鲜好物的插槽内容</div>
    </HomePanel>
    <HomePanel title="人气推荐" sub-title="人气推荐 好多商品">
        <div>我是人气推荐的插槽内容</div>
    </HomePanel>
</template>

2.无限加载实现

类似于tb,pdd在你搜索一个物品的时候,出现的商品列表滑不到底,无限加载商品(加载到数据库最后一条数据)

基础思路:

  1. 触底条件满足之后 page++,拉取下一页数据
  2. 新老数据做数组拼接
  3. 判断是否已经全部加载完毕,停止监听
<script setup lang="ts">
//展示核心代码,getList为初始页面请求返回的结果 已经省略
 
const disabled = ref(false as boolean);
const load = async() =>{
  //获取下一页的数据
  reqData.value.page++
  const res = await getSubCategoryAPI(reqData.value);
  goodList.value = [...goodList.value, ...res.result.items];
  //加载完毕,停止监听
  if(res.result.items.length == 0){
    disabled.value = true
  }
} 
</script>

3.基于业务逻辑的函数拆分

基本思想:把组件内独立的业务逻辑通过 useXXX 函数做封装处理,在组件中做组合使用
在这里插入图片描述

useBanner

import { ref, onMounted} from 'vue'
import { BannerResult } from '@/api/model/homeModel';
import { getBannerAPI } from '@/api/home';
export function useBanner(){
    //获取轮播图数据
    const bannerList = ref([] as BannerResult[])
    const getBannerList = async() =>{
    const res = await getBannerAPI( {distributionSite : '2'});
    bannerList.value = res.result;
    }
    onMounted(() => { 
        getBannerList();
    })
    return {
        bannerList
    }
}

useCategory

import { ref, onMounted } from 'vue'
import { getCategoryAPI } from '@/api/category';
import { Category } from '@/api/model/categoryModel';
import { useRoute } from 'vue-router';
import { onBeforeRouteUpdate } from 'vue-router';


export function useCategory(){
    //获取分类数据
    const route = useRoute();
    const categoryData = ref({} as Category)
    const getCategoryDate = async(id = route.params.id as string) => {
        const res = await getCategoryAPI(id)
        categoryData.value = res.result;
    }

    //目标:路由参数变化的时候,可以把分类数据接口重新发送
    onBeforeRouteUpdate((to) => {
        getCategoryDate(to.params.id as string)
    })
    return {
        categoryData
    }
}

index.vue

import { useBanner } from './composables/useBanner'
import { useCategory } from './composables/useCategory'

const { bannerList } = useBanner()
const { categoryData } = useCategory() 

VueUse

1.图片懒加载

场景:电商网站的首页通常会很长,用户不一定能访问到页面靠下面的图片,这类图片通过懒加载优化手段可以做到只有进入视口区域才会加载图片

核心原理:图片进入视口才会加载图片

基于useIntersectionObserver | VueUse
在这里插入图片描述

(1).封装全局指令

// 定义懒加载插件
import { useIntersectionObserver } from '@vueuse/core'

export const lazyPlugin = {
  install (app) {
    // 懒加载指令逻辑
    app.directive('img-lazy', {
      mounted (el, binding) {
        // el: 指令绑定的那个元素 img
        // binding: binding.value  指令等于号后面绑定的表达式的值  图片url
        console.log(el, binding.value)
        const { stop } = useIntersectionObserver(
          el,
          ([{ isIntersecting }]) => {
            console.log(isIntersecting)
            if (isIntersecting) {
              // 进入视口区域
              el.src = binding.value
              stop()
            }
          },
        )
      }
    })
  }
}

(2).注册全局指令

// 全局指令注册
import { directivePlugin } from '@/directives'
app.use(directivePlugin)

(3).替换指令

<template>
	 <!-- 指令替换:将img的src 替换为 新定义的全局组件 v-img-lazy -->	
     <img v-img-lazy="goods.picture" alt="" />
</template>

2.图片预览组件封装

1).小图切换大图

<script>
    defineProps<{
      imageList:string[];
    }>();
    //1.小图切换大图显示
    const activeIndex = ref(0)

    const enterhandler = (i:number) => {
        activeIndex.value = i;
    }
</script>

<template>
  <div class="goods-image">
    <!-- 左侧大图-->
    <div class="middle" ref="target">
      <img :src="imageList[activeIndex]" alt="" />
    </div>
    <!-- 小图列表 -->
    <ul class="small">
      <li v-for="(img, i) in imageList" :key="i" @mouseenter="enterhandler(i)" :class="{ acitve : i === activeIndex}">
        <img :src="img" alt="" />
      </li>
    </ul>
  </div>
</template>

2).放大镜效果显示

在这里插入图片描述

基于useMouseInElement | VueUse

<script setup lang="ts">
import { ref,watch } from 'vue';
import { useMouseInElement } from '@vueuse/core'

defineProps<{
  imageList:string[];
}>();

//1.小图切换大图显示
const activeIndex = ref(0)

const enterhandler = (i:number) => {
    activeIndex.value = i;
}
//2.获取鼠标相对位置 
const target = ref(null)
const { elementX, elementY, isOutside } = useMouseInElement(target)

// 3. 控制滑块跟随鼠标移动(监听elementX/Y变化,一旦变化 重新设置left/top)
const left = ref(0)
const top = ref(0)

const positionX = ref(0)
const positionY = ref(0)
watch([elementX, elementY, isOutside], () => {
  // console.log('xy变化了')
  // 如果鼠标没有移入到盒子里面 直接不执行后面的逻辑
  if (isOutside.value) return
  // console.log('后续逻辑执行了')
  // 有效范围内控制滑块距离
  // 横向
  if (elementX.value > 100 && elementX.value < 300) {
    left.value = elementX.value - 100
  }
  // 纵向
  if (elementY.value > 100 && elementY.value < 300) {
    top.value = elementY.value - 100
  }

  // 处理边界
  if (elementX.value > 300) { left.value = 200 }
  if (elementX.value < 100) { left.value = 0 }

  if (elementY.value > 300) { top.value = 200 }
  if (elementY.value < 100) { top.value = 0 }

  // 控制大图的显示
  positionX.value = -left.value * 2
  positionY.value = -top.value * 2

})

</script>


<template>
  <div class="goods-image">
    <!-- 左侧大图-->
    <div class="middle" ref="target">
      <img :src="imageList[activeIndex]" alt="" />
      <!-- 蒙层小滑块 -->
      <div class="layer" v-show="!isOutside" :style="{ left: `${left}px`, top: `${top}px` }"></div>
    </div>
    <!-- 小图列表 -->
    <ul class="small">
      <li v-for="(img, i) in imageList" :key="i" @mouseenter="enterhandler(i)" :class="{ acitve : i === activeIndex}">
        <img :src="img" alt="" />
      </li>
    </ul>
    <!-- 放大镜大图 -->
    <div class="large" :style="[
      {
        backgroundImage: `url(${imageList[activeIndex]})`,
        backgroundPositionX: `${positionX}px`,
        backgroundPositionY: `${positionY}px`,
      },
    ]" v-show="!isOutside"></div>
  </div>
</template>

vueRoute

1.路由缓存问题

相应路由参数的变化

使用带有参数的路由时需要注意的时,当用户从/users/johnny导航到/users/jolyne时,相同的组件实例将被重复使用。因为两个路由都渲染同个组件,比起销毁再创建,复用显得更加高效,不过,这也意味这组件的声明周期钩子不会被调用

问题:一级分类的切换正好满足上面的条件,组件实例复用,导致分类数据无法更新

解决思路:

一、让组件实例不复用,强制销毁重建

<RouterView :key="$router.fullPath" />

二、监听变化,变化之后执行数据更新操作

<script setup lang="ts">
	import { onBeforeRouteUpdate } from 'vue-router';

    const route = useRoute();
    const categoryData = ref({} as Category)
    const getCategoryDate = async(id = route.params.id as string) => {
        const res = await getCategoryAPI(id)
        categoryData.value = res.result;
    }
    
    //目标:路由参数变化的时候,可以把分类数据接口重新发送
    onBeforeRouteUpdate((to) => {
      getCategoryDate(to.params.id as string)
    })
</script>

2.路由行为定制

解决问题:在用户切换一级路由时,页面不会返回到顶部的情况

//路由滚动行为定制
scrollBehavior() {
    return {
        top:0
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
你好!关于 Vue 和 Koa2 的电商实战项目,我可以为你提供一些指导和建议。首先,你可以按照以下步骤进行开发: 1. 确定项目需求:明确电商项目的功能和特点,例如用户注册、登录、商品展示、购物车、订单管理、支付等。 2. 搭建前端框架:使用 Vue.js 搭建用户界面,可以选择使用 Vue CLI 来快速创建项目。 3. 设计数据库:根据项目需求,设计数据库结构,包括用户信息、商品信息、购物车、订单等。 4. 搭建后端服务:使用 Koa2 框架来搭建后端服务,处理前端发送的请求,与数据库进行交互。 5. 开发接口:根据前端的需求,编写相应的接口,提供数据的增删改查功能。 6. 实现用户认证:使用 JWT 或其他认证方式来实现用户注册、登录等功能,并验证用户的身份。 7. 构建商品展示页面:根据数据库中的商品信息,展示商品列表,并提供搜索、筛选等功能。 8. 实现购物车功能:用户可以将商品加入购物车,修改购物车中的商品数量,生成订单等。 9. 处理支付功能:集成第三方支付平台,处理用户的支付请求并生成订单。 10. 完善订单管理:实现订单列表、订单详情、订单状态管理等功能。 以上是一个基本的开发流程,当然具体实现还需根据项目需求进行调整和补充。在开发过程中,你可以参考一些相关的文档或教程,比如 Vue 官方文档、Koa2 官方文档、以及一些电商实战项目的教程和示例代码,可以帮助你更好地理解和实践这个项目。 祝你顺利完成电商实战项目的开发!如果你还有其他问题,请随时提出。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值