目录
登录-表单校验实现
表单如何进行校验
表单校验步骤
自定义校验规则
整个表单的内容验证
登录-基础登录业务实现
登录业务流程
Pinia管理用户数据
如何使用Pinia管理数据
关键代码总结
登录-Pinia用户数据持久化
持久化用户数据说明
编辑关键步骤总结和插件运行机制
登录-请求拦截器携带Token
为什么要在请求拦截器携带Token
如何配置
登录-退出登录功能实现
退出登录业务实现
购物车功能实现
购物车业务逻辑梳理拆解
本地购物车 - 加入购物车实现
1. 添加购物车
2. 删除功能实现
3列表购物车-单选功能实现
4.列表购物车-全选功能实现
5.列表购物车-统计数据功能实现
总结
登录-表单校验实现
为什么需要校验
作用:前端提前校验可以省去一些错误的请求提交,为后端节省接口压力

表单如何进行校验
ElementPlus表单组件内置了表单校验功能,只需要按照组件要求配置必要参数即可(直接看文档)
思想:当功能很复杂时,通过多个组件各自负责某个小功能,再组合成一个大功能是组件设计中的常用方法 高级软件人才培训

表单校验步骤

自定义校验规则
ElementPlus表单组件内置了初始的校验配置,应付简单的校验只需要通过配置即可,如果想要定制一些特殊的校验需 求,可以使用自定义校验规则,格式如下:
校验逻辑:如果勾选了协议框,通过校验,如果没有勾选,不通过校验

整个表单的内容验证
思考:每个表单域都有自己的校验触发事件,如果用户一上来就点击登录怎么办呢? 答:在点击登录时需要对所有需要校验的表单进行统一校验

<script setup>
import { ref } from 'vue'
// 表单数据对象
const userInfo = ref({
  account: '1311111111',
  password: '123456',
  agree: true
})
// 规则数据对象
const rules = {
  account: [
    { required: true, message: '用户名不能为空' }
  ],
  password: [
    { required: true, message: '密码不能为空' },
    { min: 6, max: 24, message: '密码长度要求6-14个字符' }
  ],
  agree: [
    {
      validator: (rule, val, callback) => {
        return val ? callback() : new Error('请先同意协议')
      }
    }
  ]
}
</script>
<template>
    <div class="form">
      <el-form ref="formRef" :model="userInfo" :rules="rules" status-icon>
        <el-form-item prop="account" label="账户">
          <el-input v-model="userInfo.account" />
        </el-form-item>
        <el-form-item prop="password" label="密码">
          <el-input v-model="userInfo.password" />
        </el-form-item>
        <el-form-item prop="agree" label-width="22px">
          <el-checkbox v-model="userInfo.agree" size="large">
            我已同意隐私条款和服务条款
          </el-checkbox>
        </el-form-item>
        <el-button size="large" class="subBtn" >点击登录</el-button>
      </el-form>
    </div>
</template>
登录-基础登录业务实现
登录业务流程

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

如何使用Pinia管理数据
遵循理念:和数据相关的所有操作(state + action)都放到Pinia中,组件只负责触发action函数

关键代码总结

import { defineStore } from 'pinia'
import { ref } from 'vue'
import { loginAPI } from '@/apis/login'
import { mergeCartAPI } from '@/apis/cart'
import { useCartStore } from '@/stores/cart'
export const useUserStore = defineStore('user', () => {
    // 1. 定义管理用户数据的state
    const userInfo = ref({})
    const CartStore = useCartStore()
    // 2. 定义获取接口数据的action函数
    const getUserInfo = async ({ account, password }) => {
        const res = await loginAPI({ account, password })
        userInfo.value = res.result
    // 合并购物车
        let result = CartStore.cartList.map(item => {
            return {
                skuId: item.skuId,
                selected: item.selected,
                count: item.count
            }
        })
        mergeCartAPI(result)
      //重新获取数据
        CartStore.updateNewList()
    }
    // 退出时清除用户信息
    const clearUserInfo = () => {
        userInfo.value = {}
        CartStore.clearCart()
    }
    // 3. 以对象的格式把state和action return
    return {
        getUserInfo,
        userInfo,
        clearUserInfo
    }
}, {
    persist: true,
})登录-Pinia用户数据持久化
持久化用户数据说明
1. 用户数据中有一个关键的数据叫做Token (用来标识当前用户是否登录),而Token持续一段时间才会过期 2. Pinia的存储是基于内存的,刷新就丢失,为了保持登录状态就要做到刷新不丢失,需要配合持久化进行存储
目的:保持token不丢失,保持登录状态 最终效果:操作state时会自动把用户数据在本地的localStorage也存一份,刷新的时候会从localStorage中先取
 关键步骤总结和插件运行机制
关键步骤总结和插件运行机制
 

//下载
npm i pinia-plugin-persistedstate
//注册
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
//使用
import { defineStore } from 'pinia'
export const useStore = defineStore(
  'main',
  () => {
    const someState = ref('你好 pinia')
    return { someState }
  },
  {
    persist: true,
  }
)登录-请求拦截器携带Token
为什么要在请求拦截器携带Token
Token作为用户标识,在很多个接口中都需要携带Token才可以正确获取数据,所以需要在接口调用时携带Token。另 外,为了统一控制采取请求拦截器携带的方案

如何配置
Axios请求拦截器可以在接口正式发起之前对请求参数做一些事情,通常Token数据会被注入到请求header中,格式按 照后端要求的格式进行拼接处理
// axios请求拦截器
http.interceptors.request.use(config => {
    const UserStore = useUserStore();
    const token = UserStore.userInfo.token
    if (token) {
        config.headers.Authorization = `Bearer ${token}`
    }
    return config
}, e => Promise.reject(e))
登录-退出登录功能实现
退出登录业务实现

基础思想:
- 清除用户信息
- 跳转到登录页
1- 新增清除用户信息action
 // 退出时清除用户信息
  const clearUserInfo = () => {
    userInfo.value = {}
  }2- 组件中执行业务逻辑
<script setup>
import { useUserStore } from '@/stores/userStore'
import { useRouter } from 'vue-router'
const userStore = useUserStore()
const router = useRouter()
const confirm = () => {
  console.log('用户要退出登录了')
  // 退出登录业务逻辑实现
  // 1.清除用户信息 触发action
  userStore.clearUserInfo()
  // 2.跳转到登录页
  router.push('/login')
}
</script>购物车功能实现
购物车业务逻辑梳理拆解
1. 整个购物车的实现分为俩个大分支,本地购物车操作和接口购物车操作
 2. 由于购物车数据的特殊性,采取Pinia管理购物车列表数据并添加持久化缓存

本地购物车 - 加入购物车实现

1. 添加购物车
基础思想:如果已经添加过相同的商品,就在其数量count上加一,如果没有添加过,就直接push到购物车列表中
// 封装购物车模块
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useCartStore = defineStore('cart', () => {
  // 1. 定义state - cartList
  const cartList = ref([])
  // 2. 定义action - addCart
  const addCart = (goods) => {
    console.log('添加', goods)
    // 添加购物车操作
    // 已添加过 - count + 1
    // 没有添加过 - 直接push
    // 思路:通过匹配传递过来的商品对象中的skuId能不能在cartList中找到,找到了就是添加过
    const item = cartList.value.find((item) => goods.skuId === item.skuId)
    if (item) {
      // 找到了
      item.count++
    } else {
      // 没找到
      cartList.value.push(goods)
    }
  }
  return {
    cartList,
    addCart
  }
}, {
  persist: true,
})
2. 删除功能实现
1- 添加删除action函数
  // 删除购物车
  const delCart = async (skuId) => {
      // 思路:
      // 1. 找到要删除项的下标值 - splice
      // 2. 使用数组的过滤方法 - filter
      const idx = cartList.value.findIndex((item) => skuId === item.skuId)
      cartList.value.splice(idx, 1)
  }2- 组件触发action函数并传递参数
 <i class="iconfont icon-close-new" @click="cartStore.delCart(i.skuId)"></i>3。列表购物车-单选功能实现
基本思想:通过skuId找到要进行单选操作的商品,把控制是否选中的selected字段修改为当前单选框的状态
1- 添加单选action
// 单选功能
const singleCheck = (skuId, selected) => {
  // 通过skuId找到要修改的那一项 然后把它的selected修改为传过来的selected
  const item = cartList.value.find((item) => item.skuId === skuId)
  item.selected = selected
}2- 触发action函数
<script setup>
// 单选回调
const singleCheck = (i, selected) => {
  console.log(i, selected)
  // store cartList 数组 无法知道要修改谁的选中状态?
  // 除了selected补充一个用来筛选的参数 - skuId
  cartStore.singleCheck(i.skuId, selected)
}
</script>
  
<template>
  <td>
    <!-- 单选框 -->
    <el-checkbox :model-value="i.selected" @change="(selected) => singleCheck(i, selected)" />
  </td>
</template>4.列表购物车-全选功能实现
基础思想:
- 全选状态决定单选框状态 - 遍历cartList把每一项的selected都设置为何全选框状态一致
- 单选框状态决定全选状态 - 只有所有单选框的selected都为true, 全选框才为true
1- store中定义action和计算属性
// 全选功能action
const allCheck = (selected) => {
  // 把cartList中的每一项的selected都设置为当前的全选框状态
  cartList.value.forEach(item => item.selected = selected)
}
// 是否全选计算属性
const isAll = computed(() => cartList.value.every((item) => item.selected))
2- 组件中触发aciton和使用计算属性 、
<script setup>
const allCheck = (selected) => {
  cartStore.allCheck(selected)
}
</script>
  
<template>
  <!-- 全选框 -->
  <el-checkbox :model-value="cartStore.isAll" @change="allCheck" />
</template>5.列表购物车-统计数据功能实现
// 3. 已选择数量
const selectedCount = computed(() => cartList.value.filter(item => item.selected).reduce((a, c) => a + c.count, 0))
// 4. 已选择商品价钱合计
const selectedPrice = computed(() => cartList.value.filter(item => item.selected).reduce((a, c) => a + c.count * c.price, 0))总结
本地购物车与线上购物车合并
判断有没有token 如果有就走线上 如果没有就走本地
退出登录时清除本地购物车
登录时 跟购物车合并并重新查询
import { defineStore } from 'pinia'
import { computed, ref } from 'vue'
import { useUserStore } from '@/stores/user'
import { insertCartAPI, findNewChartList, delCartAPI } from '@/apis/cart'
export const useCartStore = defineStore('cart', () => {
    const userStore = useUserStore()
    const isLogin = computed(() => userStore.userInfo.token)
    // 1. 定义state - cartList
    const cartList = ref([])
    // 获取购物车列表
    const updateNewList = async () => {
        let res = await findNewChartList()
        console.log(res, '购物车列表');
        cartList.value = res.result
    }
    // 2. 定义action - addCart
    const addCart = async (goods) => {
        const { skuId, count } = goods
        console.log('添加', goods)
        if (isLogin.value) {
            // 登录之后的加入购车逻辑
            await insertCartAPI({ skuId, count })
            updateNewList()
        } else {
            // 添加购物车操作
            // 已添加过 - count + 1
            // 没有添加过 - 直接push
            // 思路:通过匹配传递过来的商品对象中的skuId能不能在cartList中找到,找到了就是添加过
            const item = cartList.value.find((item) => goods.skuId === item.skuId)
            if (item) {
                // 找到了
                item.count++
            } else {
                // 没找到
                cartList.value.push(goods)
            }
        }
    }
    // 删除购物车
    const delCart = async (skuId) => {
        if (isLogin.value) {
            // 调用接口实现接口购物车中的删除功能
            await delCartAPI([skuId])
            updateNewList()
        } else {
            // 思路:
            // 1. 找到要删除项的下标值 - splice
            // 2. 使用数组的过滤方法 - filter
            const idx = cartList.value.findIndex((item) => skuId === item.skuId)
            cartList.value.splice(idx, 1)
        }
    };
    // 单选功能
    const singleCheck = (skuId, selected) => {
        // 通过skuId找到要修改的那一项 然后把它的selected修改为传过来的selected
        const item = cartList.value.find((item) => item.skuId === skuId)
        item.selected = selected
    }
    // 全选功能action
    const allCheck = (selected) => {
        // 把cartList中的每一项的selected都设置为当前的全选框状态
        cartList.value.forEach(item => item.selected = selected)
    }
    // 清除购物车
    const clearCart = () => {
        cartList.value = []
    }
    const allCount = computed(() => cartList.value.reduce((acc, item) => acc + item.count, 0))
    const allPrice = computed(() => cartList.value.reduce((acc, item) => acc + item.price * item.count, 0))
    const isAll = computed(() => cartList.value.every((item) => item.selected))
    // 3. 已选择数量
    const selectedCount = computed(() => cartList.value.filter(item => item.selected).reduce((a, c) => a + c.count, 0))
    // 4. 已选择商品价钱合计
    const selectedPrice = computed(() => cartList.value.filter(item => item.selected).reduce((a, c) => a + c.count * c.price, 0))
    return {
        cartList,
        addCart,
        delCart,
        allCount,
        allPrice,
        singleCheck,
        allCheck,
        isAll,
        selectedCount,
        selectedPrice,
        clearCart,
        updateNewList
    }
}, {
    persist: true,
})


















