电商购物平台复盘

亮点1:商品模块:采用 “虚拟列表 + 分页加载” 方案,承载 10000 + 商品数据无卡顿;封装图片懒加载自

定义指令,有效减少页面初始加载的资源请求量,降低首屏渲染时间,优化大型页面的加载性能。

“虚拟列表 + 分页加载” 方案的实际应用

  • 业务背景:生鲜商品品类繁多(水果、蔬菜、肉类等合计超 10000 种),传统列表渲染会一次性创建所有 DOM 元素,导致页面卡顿、滚动掉帧。
  • 技术实现:
    • 虚拟列表:使用vue-virtual-scroller库,只渲染可视区域内的商品卡片(约 10-15 个),滚动时动态计算可视范围并复用 DOM 元素。
1
2
3
4
5
6
7
8
9
10
11
12
<template>
<RecycleScroller
class="goods-list"
:items="visibleGoods"
:item-size="200" // 每个商品卡片的固定高度
@visible-items-changed="handleVisibleChange"
>
<template #default="{ item }">
<GoodsCard :goods="item" /> // 商品卡片组件
</template>
</RecycleScroller>
</template>
    • 分页加载:配合后端接口实现分页,滚动到页面底部时触发加载下一页(页码 + 每页条数),同时将新数据追加到总数据池。
  • 效果:10000 + 商品数据下,DOM 节点数量从原来的 10000 + 减少到 30 个以内,滚动帧率稳定在 60fps,滑动时无卡顿感。

图片懒加载自定义指令的封装与应用

  • 业务痛点:生鲜商品图片多且清晰(单张约 200-500KB),首屏若加载全部图片会产生大量请求,导致首屏加载缓慢。

  • 实现方案:封装v-lazy自定义指令,基于IntersectionObserverAPI 监听图片是否进入视口,只加载可见区域图片。

  • // 指令定义:directives/lazy.js
    export default {
      mounted(el, binding) {
        const observer = new IntersectionObserver((entries) => {
          entries.forEach(entry => {
            if (entry.isIntersecting) {
              // 图片进入视口时加载
              el.src = binding.value  // 绑定的图片真实地址
              observer.unobserve(el)  // 加载后停止监听
            }
          })
        })
        observer.observe(el)
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27

    - 应用场景:商品列表的缩略图、分类页的 banner 图均使用`v-lazy`指令,配合占位图(1KB 以内的灰色背景图)提升感知性能。



    **亮点二: 基于 Pinia 实现 “状态 - 视图 - 操作” 解耦,拆分 8 个业务模块,通过订阅机制实现跨模块数据联**

    **动,购物车与订单数据同步误差率降至 0**

    1. **状态 - 视图 - 操作解耦的实现**

    - **状态层**:用 Pinia 定义独立的 store(如 cartStore、orderStore),只负责数据存储和核心计算(例如 cartStore 中定义商品总数、总价的 getter)
    - **视图层**:组件只通过 store 获取数据和调用方法(如购物车组件通过 cartStore.goodsList 渲染列表,点击删除按钮调用 cartStore.removeItem ())
    - **操作层**:所有数据修改逻辑封装在 store 的 actions 中(如添加商品时的库存校验、价格计算等)

    2. **8 个业务模块的拆分**按电商业务域拆分出:用户模块(userStore)、商品模块(goodsStore)、购物车模块(cartStore)、订单模块(orderStore)、地址模块(addressStore)、收藏模块(collectStore)、优惠券模块(couponStore)、全局设置模块(appStore)。每个模块只管理自身相关数据,例如 cartStore 仅维护购物车商品、选中状态等数据。

    3. **跨模块数据联动的实际应用**通过 Pinia 的订阅机制($subscribe)实现模块间通信:

    4. ```
    // orderStore中订阅cartStore的变化
    cartStore.$subscribe((mutation, state) => {
    // 当购物车商品变化时,自动更新订单确认页的商品列表和总金额
    if (mutation.type === 'cart/addItem') {
    this.syncOrderGoods(state.goodsList)
    }
    })
    1. 实际业务场景:用户在购物车修改商品数量后,订单确认页会实时更新对应商品的数量和总价,无需手动刷新。 2. **购物车与订单数据同步误差率降至 0 的效果**未优化前,由于购物车和订单数据分散在组件中,常出现 “购物车已修改但订单页未更新” 的问题(误差率约 8%)。通过 Pinia 的集中管理和订阅机制: - 所有数据变更只有唯一入口(store 的 actions) - 关联模块自动同步更新 - 配合本地持久化,解决页面刷新导致的数据不一致 最终实现从购物车到订单提交的全流程数据零误差,用户提交订单时不会出现 “商品已下架但仍能下单” 等异常情况。

亮点三:用户模块:用 vee-validate 实现表单校验,配合路由守卫实现 “未登录跳转拦截”,登录页安全性测试通过率 100%

vee-validate 表单校验的实际应用

  • 针对登录页的手机号和密码字段,使用 vee-validate 定义了严格的校验规则:

  • // 手机号校验:必须为11位数字且符合手机号规则
    defineRule('phone', (value) => {
      if (!value) return '请输入手机号'
      if (!/^1[3-9]\d{9}$/.test(value)) return '请输入正确的手机号'
      return true
    })
    // 密码校验:8-20位,包含数字和字母
    defineRule('password', (value) => {
      if (!value) return '请输入密码'
      if (value.length < 8 || value.length > 20) return '密码长度为8-20位'
      if (!/[0-9]/.test(value) || !/[a-zA-Z]/.test(value)) return '密码需包含数字和字母'
    })
    
    1
    2
    3
    4
    5

    **路由守卫实现 “未登录跳转拦截”**

    通过 Vue Router 的全局前置守卫,限制未登录用户访问需要权限的页面(如个人中心、订单页、结算页):

    // 路由守卫配置 router.beforeEach((to, from, next) => { const isLogin = userStore.isLogin // 从Pinia获取登录状态 // 需要登录的路由在meta中标记requireAuth: true if (to.meta.requireAuth && !isLogin) { // 未登录时跳转到登录页,并记录当前页面路径(登录后可跳转回来) next({ path: '/login', query: { redirect: to.fullPath } }) } else { next() } })
    • 实际效果:用户点击 “去结算” 按钮时,若未登录会自动跳转到登录页,登录成功后再返回结算页,既保证了权限安全,又不影响用户操作流程。