在電商項目中,購物車是核心功能之一,需要兼顧數據一致性、用戶體驗和邏輯復雜度。
本文結合 Vue3 + Pinia 技術棧,詳細講解如何實現一個高效且易用的購物車系統,重點剖析?添加購物車?和?頭部購物車預覽?的核心邏輯與實現細節。
一、技術選型與核心思路
1. 技術棧選擇
- 狀態管理:使用?Pinia?實現購物車數據的集中管理,支持持久化存儲(通過?
pinia-plugin-persistedstate
),確保刷新頁面數據不丟失。 - 組件庫:基于 Element Plus 實現表單、按鈕等交互組件,結合自定義 CSS 實現懸浮層動畫和復雜布局。
- 接口交互:通過 Axios 封裝購物車相關接口,區分登錄 / 未登錄狀態,實現本地臨時存儲與服務器數據的無縫切換。
2. 核心設計思路
- 雙模式處理:未登錄用戶將購物車數據暫存于本地 Pinia,登錄后同步至服務器;已登錄用戶直接操作服務器接口,保證數據一致性。
- 響應式渲染:利用 Pinia 的響應式特性,購物車數據變化時自動更新組件視圖,無需手動操作 DOM。
- 用戶體驗優化:通過懸浮層動畫、漸變顯示刪除按鈕、實時計算總價等細節,提升交互流暢度。
二、添加購物車:分場景數據處理
1. Pinia 狀態定義與 Action 封裝
在?cartStore.js
?中定義購物車狀態?cartList
,并封裝?addCart
?方法處理添加邏輯:
// src/stores/cartStore.js
import { defineStore, ref, computed } from 'pinia';
import { useUserStore } from './userStore';
import { insertCartAPI } from '@/apis/cart';export const useCartStore = defineStore('cart', () => {const userStore = useUserStore();const isLogin = computed(() => userStore.userInfo.token); // 判斷登錄狀態const cartList = ref([]); // 購物車商品列表// 添加購物車(核心邏輯)const addCart = async (goods) => {const { skuId, count } = goods;if (isLogin.value) {// 已登錄:調用接口添加至服務器購物車await insertCartAPI({ skuId, count });updateNewList(); // 同步最新購物車數據} else {// 未登錄:本地處理(存在則數量+1,否則新增)const item = cartList.value.find((item) => item.skuId === skuId);if (item) {// 找到了item.count++} else {// 沒找到cartList.value.push(goods)}}};// 獲取最新購物車列表(登錄狀態專用)const updateNewList = async () => {const res = await findNewCartListAPI(); // 接口調用cartList.value = res.result;};return { cartList, addCart, updateNewList };
}, { persist: true }); // 啟用持久化存儲,數據自動同步至 localStorage
2. 接口封裝與調用
在?cart.js
?中定義添加購物車的接口,遵循 RESTful 規范:
// src/apis/cart.js
import request from '@/utils/http';// 加入購物車接口(登錄狀態)
export const insertCartAPI = ({ skuId, count }) => {return request({url: '/member/cart',method: 'POST',data: { skuId, count },});
};
3. 組件觸發添加操作
在商品詳情頁或按鈕點擊事件中,構造商品對象并調用?addCart
:
// 示例:商品詳情頁添加按鈕
const handleAddToCart = () => {const goods = {skuId: product.skuId, // 商品唯一標識count: 1, // 默認添加數量name: product.name,price: product.price,picture: product.mainPictures[0], // 主圖attrsText: product.specsText, // 規格信息(如顏色、尺碼)selected: true, // 默認選中該商品};const cartStore = useCartStore();cartStore.addCart(goods); // 觸發添加邏輯
};
三、頭部購物車預覽:懸浮層交互與數據渲染
1. 組件結構與數據綁定
通過?HeadCart.vue
?實現頁面頭部的購物車圖標懸浮層,實時展示購物車商品列表:
<!-- src/components/HeadCart.vue -->
<script setup>
import { useCartStore } from '@/stores/cartStore';
const cartStore = useCartStore();
</script><template><div class="cart"><!-- 購物車圖標與未讀數量 --><a class="curr" href="javascript:;"><i class="iconfont icon-cart"></i><em>{{ cartStore.cartList.length }}</em> <!-- 實時顯示商品總數 --></a><!-- 懸浮層(鼠標懸停顯示) --><div class="layer"><div class="list"><!-- 商品列表循環渲染 --><div class="item" v-for="item in cartStore.cartList" :key="item.skuId"><RouterLink to="/cartlist"> <!-- 點擊跳轉購物車詳情頁 --><img :src="item.picture" alt="" /> <!-- 商品圖片 --><div class="center"><p class="name ellipsis-2">{{ item.name }}</p> <!-- 商品名稱 --><p class="attr ellipsis">{{ item.attrsText }}</p> <!-- 規格信息 --></div><div class="right"><p class="price">¥{{ item.price }}</p> <!-- 單價 --><p class="count">x{{ item.count }}</p> <!-- 數量 --></div></RouterLink><!-- 刪除按鈕(懸停顯示) --><i class="iconfont icon-close-new" @click="cartStore.delCart(item.skuId)"></i></div></div><!-- 底部操作欄 --><div class="foot"><div class="total"><p>共 {{ cartStore.allCount }} 件商品</p> <!-- 總數量(計算屬性) --><p>¥ {{ cartStore.allPrice.toFixed(2) }}</p> <!-- 總價(計算屬性) --></div><el-button @click="$router.push('/cartlist')">去購物車結算</el-button></div></div></div>
</template>
2. 計算屬性:實時統計數據
在?cartStore.js
?中通過?computed
?實現總數量和總價的實時計算:
// cartStore.js 補充計算屬性
const allCount = computed(() => cartList.value.reduce((acc, item) => acc + item.count, 0)
); // 總商品數(各商品數量累加)const allPrice = computed(() => cartList.value.reduce((acc, item) => acc + item.count * item.price, 0)
); // 總價(數量×單價累加)
3. 刪除功能:本地與接口雙模式處理
在?cartStore.js
?中封裝?delCart
?方法,根據登錄狀態執行不同邏輯:
// cartStore.js 刪除邏輯
const delCart = async (skuId) => {if (isLogin.value) {// 已登錄:調用接口刪除(支持批量刪除)await delCartAPI([skuId]); // 傳遞商品ID數組updateNewList(); // 刷新購物車列表} else {// 未登錄:本地刪除(通過下標移除)const idx = cartList.value.findIndex((item) => item.skuId === skuId);cartList.value.splice(idx, 1);}
};
4. 樣式實現:懸浮層動畫與細節優化
通過 SCSS 實現懸浮層的漸變顯示、刪除按鈕懸停顯示等效果:
/* 懸浮層初始隱藏,懸停時漸變顯示 */
.cart .layer {opacity: 0;transform: translateY(-200px) scale(1, 0); /* 向上偏移并縮放隱藏 */transition: all 0.4s 0.2s; /* 動畫過渡 */&:hover {opacity: 1;transform: none; /* 恢復原位 */}
}/* 刪除按鈕默認隱藏,懸停商品時顯示 */
.item i {opacity: 0;transition: all 0.5s;&:hover {cursor: pointer;}
.item:hover i { opacity: 1; }/* 滾動條樣式自定義 */
.list {overflow: auto;&::-webkit-scrollbar-thumb {background: #eee;border-radius: 10px;}
}
四、核心技術點總結
1. Pinia 狀態管理優勢
- 響應式:通過?
ref
?定義的?cartList
?自動觸發組件重新渲染,無需手動調用?forceUpdate
。 - 持久化:配置?
persist: true
?后,數據自動存儲至?localStorage
,刷新頁面或重啟瀏覽器后數據依然存在。 - 模塊化:將購物車相關的?
state
、action
、computed
?集中在?cartStore
?中,方便后續擴展(如合并登錄前后的購物車數據)。
2. 登錄與未登錄邏輯分離
- 未登錄:所有操作(添加、刪除)均在本地?
cartList
?中進行,避免無效的網絡請求,提升離線體驗。 - 已登錄:通過接口與服務器交互,確保多設備數據同步,同時在操作后刷新本地列表,保持數據一致。
3. 用戶體驗細節
- 實時反饋:頭部懸浮層實時顯示商品總數和總價,無需跳轉頁面即可查看購物車狀態。
- 交互動畫:懸浮層的漸變顯示和刪除按鈕的漸顯效果,增強操作反饋,減少用戶認知成本。
- 響應式布局:通過彈性盒子(Flex)和百分比單位,確保懸浮層在不同屏幕尺寸下正常顯示。
五、總結與實現效果
頭部購物車添加刪除
本文通過?Pinia 狀態管理?和?分場景邏輯處理,實現了一個健壯且易用的購物車系統。后續可進一步擴展以下功能:
- 全選與單選功能:通過?
selected
?字段標記商品選中狀態,結合計算屬性實現全選邏輯。 - 數量增減組件:使用 Element Plus 的?
InputNumber
?組件,支持用戶直接修改購物車商品數量。 - 合并購物車:用戶登錄后,將本地臨時購物車數據與服務器數據合并,避免重復添加。
購物車功能的核心在于?數據一致性?和?用戶體驗?的平衡,通過合理的狀態管理和清晰的邏輯分層,能夠有效降低開發復雜度,提升項目可維護性。