SKU 模塊 - 下載 SKU 插件
DCloud 插件市場? 是 uni-app?官方插件生態集中地,有數千款插件
使用SKU插件:
組件安裝到自己的項目
注意事項:項目進行 git 提交時會校驗文件,可添加?/* eslint-disable */? 禁用檢查
<script>
/* eslint-disable */
// 省略組件源代碼
</script>
打開購物車彈框,渲染商品信息? ?goods.vue
<!-- SKU 彈窗組件 -->
? <vk-data-goods-sku-popup v-model="isShowSku" :localdata="localdata" />
// 是否顯示 SKU 組件
const isShowSku = ref(false)
// 商品信息
const localdata = ref({} as SkuPopupLocaldata)
//? 渲染商品信息
// 獲取商品詳情信息
const goods = ref<GoodsResult>()
const getGoodsByIdData = async () => {
? const res = await getGoodsByIdAPI(query.id)
? goods.value = res.result
? // SKU 組件所需格式
? localdata.value = {
? ? _id: res.result.id,
? ? name: res.result.name,
? ? goods_thumb: res.result.mainPictures[0],
? ? spec_list: res.result.specs.map((v) => {
? ? ? return {
? ? ? ? name: v.name,
? ? ? ? list: v.values,
? ? ? }
? ? }),
? ? sku_list: res.result.skus.map((v) => {
? ? ? return {
? ? ? ? _id: v.id,
? ? ? ? goods_id: res.result.id,
? ? ? ? goods_name: res.result.name,
? ? ? ? image: v.picture,
? ? ? ? price: v.price * 100, // 注意:需要乘以 100
? ? ? ? stock: v.inventory,
? ? ? ? sku_name_arr: v.specs.map((vv) => vv.valueName),
? ? ? }
? ? }),
? }
}
打開sku 彈窗? ? ? ?渲染商品
打開SKU彈窗? =》 設置按鈕模式? ? =》 微調組件樣式
<!-- SKU 彈窗組件 -->
? <vk-data-goods-sku-popup
? ? v-model="isShowSku"
? ? :localdata="localdata"
? ? :mode="mode"
? ? add-cart-background-color="#ffa868"
? ? buy-now-background-color="#27ba98"
? ? :active-style="{
? ? ? color: '#27ba9b',
? ? ? borderColor: '#27ba9b',
? ? ? backgroundCColor: '#e9f8f5',
? ? }"
? />
// mode? 設置按鈕模式
//?add-cart-background-color? ?設置即入購物車按鈕背景色
//?buy-now-background-color? ?設置立即購買按鈕背景色
//?:active-style? 選擇商品規格時的激活樣式
// 按鈕模式? ? ? ?枚舉
enum SkuMode {
? Both = 1, // 購物車和立即購買都顯示
? Cart = 2, // 只顯示購物車
? Buy = 3, // 只顯示立即購買
}
const mode = ref<SkuMode>(SkuMode.Both)
// 打開sku 彈窗 修改按鈕模式
const openSkuPopup = (val: SkuMode) => {
? // 顯示sku組件
? isShowSku.value = true
? // 修改按鈕模式
? mode.value = val
}
<view class="item arrow" @tap="openSkuPopup(SkuMode.Both)">
? ? ? ? ? <text class="label">選擇</text>
? ? ? ? ? <text class="text ellipsis"> 請選擇商品規格 </text>
</view>
<view class="buttons">
? ? ? <view class="addcart" @tap="openSkuPopup(SkuMode.Cart)"> 加入購物車 </view>
? ? ? <view class="buynow" @tap="openSkuPopup(SkuMode.Buy)"> 立即購買 </view>
</view>
加入購物車事件? ? ? ? 加入購物車在商品詳情頁面? ? ? ?goods.vue
<!-- SKU 彈窗組件 -->
? <vk-data-goods-sku-popup
? ? v-model="isShowSku"
? ? :localdata="localdata"
? ? :mode="mode"
? ? add-cart-background-color="#ffa868"
? ? buy-now-background-color="#27ba9b"
? ? ref="skuPopupRef"
? ? :actived-style="{
? ? ? color: '#27BA9B',
? ? ? borderColor: '#27BA9B',
? ? ? backgroundColor: '#E9F8F5',
? ? }"
? ? @add-cart="onAddCart"
? />
// 加入購物車事件
const onAddCart = (e: SkuPopupEvent) => {
? console.log(e)
}
控制臺打印數據
封裝購物車接口:cart.ts
1、加入購物車接口封裝
import { http } from "@/utils/http"
/**
?* 加入購物車
?* @param data 請求體參數
?* @returns
?*/
export const postMemberCartAPI = (data: { skuId: string; count: number}) => {
? return http({
? ? method: 'POST',
? ? url: '/member/cart',
? ? data,
? })
}
完善商品詳情頁面的加入購物車功能
// 加入購物車事件
const onAddCart = async (e: SkuPopupEvent) => {
? console.log(e)
? await postMemberCartAPI({ skuId: e._id, count: e.buy_num })
? uni.showToast({ icon: 'success', title: '已加入購物車' })
? // 關閉彈窗
? isShowSku.value = false
}
完整的商品詳情頁面代碼:goods.vue
<script setup lang="ts">
import { onLoad } from '@dcloudio/uni-app'
import { ref, computed } from 'vue'
import { getGoodsByIdAPI } from '@/services/goods'
import { postMemberCartAPI } from '@/services/cart'
import type { GoodsResult } from '@/types/goods'
import AddressPanel from './components/AddressPanel.vue'
import ServicePanel from './components/ServicePanel.vue'
import PageSkeleton from './components/PageSkeleton.vue'
import type {SkuPopupEvent,SkuPopupInstanceType,SkuPopupLocaldata,
} from '@/components/vk-data-goods-sku-popup/vk-data-goods-sku-popup'// 獲取屏幕邊界到安全區域距離
const { safeAreaInsets } = uni.getSystemInfoSync()// 接收頁面參數
const query = defineProps<{id: string
}>()// 獲取商品詳情信息
const goods = ref<GoodsResult>()
const getGoodsByIdData = async () => {const res = await getGoodsByIdAPI(query.id)goods.value = res.result// SKU 組件所需格式localdata.value = {_id: res.result.id,name: res.result.name,goods_thumb: res.result.mainPictures[0],spec_list: res.result.specs.map((v) => {return {name: v.name,list: v.values,}}),sku_list: res.result.skus.map((v) => {return {_id: v.id,goods_id: res.result.id,goods_name: res.result.name,image: v.picture,price: v.price * 100, // 注意:需要乘以 100stock: v.inventory,sku_name_arr: v.specs.map((vv) => vv.valueName),}}),}
}// 是否數據加載完成
const isFinish = ref(false)// 頁面加載
onLoad(async () => {await getGoodsByIdData()isFinish.value = true
})// 輪播圖變化時
const currentIndex = ref(0)
const onChange: UniHelper.SwiperOnChange = (e) => {currentIndex.value = e.detail!.current
}// 點擊圖片時
const onTapImage = (url: string) => {// 大圖預覽uni.previewImage({current: url, // 當前顯示圖片的鏈接urls: goods.value!.mainPictures, // 需要預覽的圖片鏈接列表 數組})
}
// uni-ui 彈出層組件 ref
const popup = ref<{open: (type?: UniHelper.UniPopupType) => voidclose: (type?: UniHelper.UniPopupType) => void
}>()// 彈出層渲染
const popupName = ref<'address' | 'service'>()
const openPopup = (name: typeof popupName.value) => {// 修改彈出層名稱popupName.value = namepopup.value?.open()
}// 是否顯示 SKU 組件
const isShowSku = ref(false)
// 商品信息
const localdata = ref({} as SkuPopupLocaldata)
// 按鈕模式
enum SkuMode {Both = 1, // 購物車和立即購買都顯示Cart = 2, // 只顯示購物車Buy = 3, // 只顯示立即購買
}
const mode = ref<SkuMode>(SkuMode.Both)
// 打開sku 彈窗 修改按鈕模式
const openSkuPopup = (val: SkuMode) => {// 顯示sku組件isShowSku.value = true// 修改按鈕模式mode.value = val
}
// SKU組件實例
const skuPopupRef = ref<SkuPopupInstanceType>()// 計算被選中的值
const selectArrText = computed(() => {return skuPopupRef.value?.selectArr?.join(' ').trim() || '請選擇商品規格'
})
// 加入購物車事件
const onAddCart = async (e: SkuPopupEvent) => {console.log(e)await postMemberCartAPI({ skuId: e._id, count: e.buy_num })uni.showToast({ icon: 'success', title: '已加入購物車' })// 關閉彈窗isShowSku.value = false
}
</script><template><!-- SKU 彈窗組件 --><vk-data-goods-sku-popupv-model="isShowSku":localdata="localdata":mode="mode"add-cart-background-color="#ffa868"buy-now-background-color="#27ba9b"ref="skuPopupRef":actived-style="{color: '#27BA9B',borderColor: '#27BA9B',backgroundColor: '#E9F8F5',}"@add-cart="onAddCart"/><scroll-view scroll-y class="viewport" v-if="isFinish"><!-- 基本信息 --><view class="goods"><!-- 商品主圖 --><view class="preview"><swiper circular @change="onChange"><swiper-item v-for="item in goods?.mainPictures" :key="item"><image @tap="onTapImage(item)" mode="aspectFill" :src="item" /></swiper-item></swiper><view class="indicator"><text class="current">{{ currentIndex + 1 }}</text><text class="split">/</text><text class="total">{{ goods?.mainPictures.length }}</text></view></view><!-- 商品簡介 --><view class="meta"><view class="price"><text class="symbol">¥</text><text class="number">{{ goods?.price }}</text></view><view class="name ellipsis">{{ goods?.name }} </view><view class="desc"> {{ goods?.desc }} </view></view><!-- 操作面板 --><view class="action"><view class="item arrow" @tap="openSkuPopup(SkuMode.Both)"><text class="label">選擇</text><text class="text ellipsis"> {{ selectArrText }} </text></view><view class="item arrow" @tap="openPopup('address')"><text class="label">送至</text><text class="text ellipsis"> 請選擇收獲地址 </text></view><view class="item arrow" @tap="openPopup('service')"><text class="label">服務</text><text class="text ellipsis"> 無憂退 快速退款 免費包郵 </text></view></view></view><!-- 商品詳情 --><view class="detail panel"><view class="title"><text>詳情</text></view><view class="content"><view class="properties"><!-- 屬性詳情 --><view class="item" v-for="item in goods?.details.properties" :key="item.name"><text class="label">{{ item.name }}</text><text class="value">{{ item.value }}</text></view></view><!-- 圖片詳情 --><imagev-for="item in goods?.details.pictures":key="item"mode="widthFix":src="item"></image></view></view><!-- 同類推薦 --><view class="similar panel"><view class="title"><text>同類推薦</text></view><view class="content"><navigatorv-for="item in goods?.similarProducts":key="item"class="goods"hover-class="none":url="`/pages/goods/goods?id=${item.id}`"><image class="image" mode="aspectFill" :src="item.picture"></image><view class="name ellipsis">{{ item.name }}</view><view class="price"><text class="symbol">¥</text><text class="number">{{ item.price }}</text></view></navigator></view></view></scroll-view><PageSkeleton v-else /><!-- 用戶操作 --><view class="toolbar" :style="{ paddingBottom: safeAreaInsets?.bottom + 'px' }"><view class="icons"><button class="icons-button"><text class="icon-heart"></text>收藏</button><button class="icons-button" open-type="contact"><text class="icon-handset"></text>客服</button><navigator class="icons-button" url="/pages/cart/cart" open-type="switchTab"><text class="icon-cart"></text>購物車</navigator></view><view class="buttons"><view class="addcart" @tap="openSkuPopup(SkuMode.Cart)"> 加入購物車 </view><view class="buynow" @tap="openSkuPopup(SkuMode.Buy)"> 立即購買 </view></view></view><!-- uni-ui 彈出層 --><uni-popup ref="popup" type="bottom"><AddressPanel v-if="popupName === 'address'" @close="popup?.close()" /><ServicePanel v-if="popupName === 'service'" @close="popup?.close()" /></uni-popup>
</template>
購物車列表頁面:cart.vue
獲取登錄的用戶信息? -->? 條件渲染(是否登錄)? -->? 初始化調用? -->? 列表渲染
?
封裝購物車列表類型數據:cart.d.ts
/** 購物車類型 */
export type CartItem = {
? /** 商品 ID */
? id: string
? /** SKU ID */
? skuId: string
? /** 商品名稱 */
? name: string
? /** 圖片 */
? picture: string
? /** 數量 */
? count: number
? /** 加入時價格 */
? price: number
? /** 當前的價格 */
? nowPrice: number
? /** 庫存 */
? stock: number
? /** 是否選中 */
? selected: boolean
? /** 屬性文字 */
? attrsText: string
? /** 是否為有效商品 */
? isEffective: boolean
}
封裝購物車列表接口:cart.ts
import type { CartItem } from '@/types/cart';
import { http } from '@/utils/http'
/**
?* 獲取購物車列表數據
?* @returns
?*/
export const getMemberCartAPI = () => {
? return http<CartItem[]>({
? ? method: 'GET',
? ? url: '/member/cart',
? })
}
初始化調用:cart.vue
// 獲取購物車列表數據
const cartList = ref<CartItem>([])
const getMemberCartData = async () => {
? const res = await getMemberCartAPI()
? cartList.value = res.result
}
// onShow:頁面顯示就觸發 ? ? 頁面初始化調用 ?因為加入購物車不是在這個頁面的,所以用onShow調用更合適
onShow(() => {
? // 判斷用戶是否已經登錄了
? if (memberStore.profile) {
? ? getMemberCartData()
? }
})
刪除購物車列表中的商品:封裝API、按鈕綁定事件、彈窗二次確認、調用API、重新獲取列表
封裝購物車刪除API 接口:
/**
?* 刪除/清空購物車單品
?* @param data 請求體參數 ids SKUID 集合
?*/
export const deleteMemberCartAPI = (data: { ids: string[] }) => {
? return http({
? ? method: 'DELETE',
? ? url: '/member/cart',
? ? data,
? })
}
點擊刪除按鈕 - 刪除購物車商品? ?cart.vue
// 點擊刪除按鈕 - 刪除購物車
const onDeleteCart = (skuId: string) => {
? // 彈窗二次確認
? uni.showModal({
? ? content: '是否確定刪除?',
? ? success: async (res) => {
? ? ? if (res.confirm) {
? ? ? ? await deleteMemberCartAPI({ ids: [skuId] })
? ? ? ? // 更新購物車列表
? ? ? ? getMemberCartData()
? ? ? }
? ? },
? })
}
刪除成功
修改商品數量:步進器組件
<view class="count"><!-- <text class="text">-</text><input class="input" type="number" :value="item.count.toString()" /><text class="text">+</text> --><vk-data-input-number-boxv-model="item.count":min="1":max="item.stock":index="item.skuId"@change="onChangeCount"/></view>
封裝修改API
/**
?* 修改購物車單品
?* @param skuId SKUID
?* @param data selected 選中狀態 count 商品數量
?*/
export const putMemberCartBySkuIdAPI = ( skuId: string, data: { selected?: boolean; count?: number }) => {
? return http({
? ? method: 'PUT',
? ? url: `/member/cart/${skuId}`,
? ? data,
? })
}
修改方法:
// 修改商品數量
const onChangeCount = (e) => {
? console.log(e)
? putMemberCartBySkuIdAPI(e.index, { count: e.value })
}
修改商品的選中狀態,即單選和全選功能實現
<!-- 選中狀態 --><text@tap="onChangeSelected(item)"class="checkbox":class="{ checked: item.selected }"></text>
封裝全選 / 取消全選API
/**
?* 購物車全選/取消全選
?* @param data selected 是否選中
?*/
export const putMemberCartSelectedAPI = (data: { selected: boolean }) => {
? return http({
? ? method: 'PUT',
? ? url: '/member/cart/selected',
? ? data,
? })
}
// 修改選中狀態? - 單選修改
const onChangeSelected = (good: CartItem) => {
? console.log(good)
// 前端數據更新 ?- 是否選中 取反
? good.selected = !good.selected
? // 后端數據更新? ? 與修改數量接口是同一條接口? 傳遞的參數不同
? putMemberCartBySkuIdAPI(good.skuId, { selected: good.selected })
}
// 計算全選狀態
const isSelectedAll = computed(() => {
? return cartList.value.length && cartList.value.every((v) => v.selected)
})
// 修改選中狀態-全選修改
const onChangeSelectedAll = () => {
? // 全選狀態取法
? const _isSelectedAll = !isSelectedAll.value
? // 前端數據更新
? cartList.value.forEach((item) => {
? ? item.selected = _isSelectedAll
? })
? // 后端更新
? putMemberCartSelectedAPI({ selected: _isSelectedAll })
}
購物車頁面 - 底部結算信息
<!-- 底部結算 -->
? ? ? <view class="toolbar">
? ? ? ? <text class="all" @tap="onChangeSelectedAll" :class="{ checked: isSelectedAll }">全選</text>
? ? ? ? <text class="text">合計:</text>
? ? ? ? <text class="amount">{{ selectedCartListMoney }}</text>
? ? ? ? <view class="button-grounp">
? ? ? ? ? <view
? ? ? ? ? ? @tap="gotoPayment"
? ? ? ? ? ? class="button payment-button"
? ? ? ? ? ? :class="{ disabled: selectedCartListCount === 0 }"
? ? ? ? ? >
? ? ? ? ? ? 去結算({{ selectedCartListCount }})
? ? ? ? ? </view>
? ? ? ? </view>
? ? ? </view>
邏輯實現:
// 計算選中的商品列表
const selectedCartList = computed(() => {
? return cartList.value.filter((v) => v.selected)
})
// 計算選中商品的總件數
const selectedCartListCount = computed(() => {
? return selectedCartList.value.reduce((sum, item) => sum + item.count, 0)
})
// 計算選中商品的總金額
const selectedCartListMoney = computed(() => {
? return selectedCartList.value
? ? .reduce((sum, item) => sum + item.count * item.nowPrice, 0)
? ? .toFixed(2)
})
// 去結算按鈕
const gotoPayment = () => {
? // 判斷用戶是否選擇了商品 ? ?即商品數量不能為 0
? if (selectedCartListCount.value === 0) {
? ? return uni.showToast({ icon: 'none', title: '請選擇商品' })
? }
? // 跳轉到計算頁面
? uni.showToast({ title: '此功能還未寫' })
}
完整的購物車列表頁面組件代碼:cart.vue
<script setup lang="ts">
import { onShow } from '@dcloudio/uni-app'
import { ref, computed } from 'vue'
import {deleteMemberCartAPI,getMemberCartAPI,putMemberCartBySkuIdAPI,putMemberCartSelectedAPI,
} from '@/services/cart'
import { useMemberStore } from '@/stores/index'
import type { CartItem } from '@/types/cart'
import type { InputNumberBoxEvent } from '@/components/vk-data-input-number-box/vk-data-input-number-box'// 獲取會員 Store
const memberStore = useMemberStore()// 獲取購物車列表數據
const cartList = ref<CartItem>([])
const getMemberCartData = async () => {const res = await getMemberCartAPI()cartList.value = res.result
}// onShow:頁面顯示就觸發 頁面初始化調用 因為加入購物車不是在這個頁面的,所以用onShow調用更合適
onShow(() => {// 判斷用戶是否已經登錄了if (memberStore.profile) {getMemberCartData()}
})// 點擊刪除按鈕 - 刪除購物車
const onDeleteCart = (skuId: string) => {// 彈窗二次確認uni.showModal({content: '是否確定刪除?',success: async (res) => {if (res.confirm) {await deleteMemberCartAPI({ ids: [skuId] })// 更新購物車列表getMemberCartData()}},})
}// 修改商品數量
const onChangeCount = (e: InputNumberBoxEvent) => {console.log(e)putMemberCartBySkuIdAPI(e.index, { count: e.value })
}// 修改選中狀態 - 單品修改
const onChangeSelected = (good: CartItem) => {console.log(good)// 前端數據更新 - 是否選中 取反good.selected = !good.selected// 后端數據更新putMemberCartBySkuIdAPI(good.skuId, { selected: good.selected })
}// 計算全選狀態
const isSelectedAll = computed(() => {return cartList.value.length && cartList.value.every((v) => v.selected)
})// 修改選中狀態-全選修改
const onChangeSelectedAll = () => {// 全選狀態取法const _isSelectedAll = !isSelectedAll.value// 前端數據更新cartList.value.forEach((item) => {item.selected = _isSelectedAll})// 后端更新putMemberCartSelectedAPI({ selected: _isSelectedAll })
}// 計算選中的商品列表
const selectedCartList = computed(() => {return cartList.value.filter((v) => v.selected)
})// 計算選中商品的總件數
const selectedCartListCount = computed(() => {return selectedCartList.value.reduce((sum, item) => sum + item.count, 0)
})// 計算選中商品的總金額
const selectedCartListMoney = computed(() => {return selectedCartList.value.reduce((sum, item) => sum + item.count * item.nowPrice, 0).toFixed(2)
})// 去結算按鈕
const gotoPayment = () => {// 判斷用戶是否選擇了商品 即商品數量不能為 0if (selectedCartListCount.value === 0) {return uni.showToast({ icon: 'none', title: '請選擇商品' })}// 跳轉到計算頁面uni.showToast({ title: '此功能還未寫' })
}
</script><template><scroll-view scroll-y class="scroll-view"><!-- 已登錄: 顯示購物車 --><template v-if="memberStore.profile.token"><!-- 購物車列表 --><view class="cart-list" v-if="cartList.length"><!-- 優惠提示 --><view class="tips"><text class="label">滿減</text><text class="desc">滿1件, 即可享受9折優惠</text></view><!-- 滑動操作分區 --><uni-swipe-action><!-- 滑動操作項 --><uni-swipe-action-item v-for="item in cartList" :key="item.skuId" class="cart-swipe"><!-- 商品信息 --><view class="goods"><!-- 選中狀態 --><text@tap="onChangeSelected(item)"class="checkbox":class="{ checked: item.selected }"></text><navigator:url="`/pages/goods/goods?id=${item.id}`"hover-class="none"class="navigator"><image mode="aspectFill" class="picture" :src="item.picture"></image><view class="meta"><view class="name ellipsis">{{ item.name }}</view><view class="attrsText ellipsis">{{ item.attrsText }}</view><view class="price">{{ item.nowPrice }}</view></view></navigator><!-- 商品數量 --><view class="count"><!-- <text class="text">-</text><input class="input" type="number" :value="item.count.toString()" /><text class="text">+</text> --><vk-data-input-number-boxv-model="item.count":min="1":max="item.stock":index="item.skuId"@change="onChangeCount"/></view></view><!-- 右側刪除按鈕 --><template #right><view class="cart-swipe-right"><button @tap="onDeleteCart(item.skuId)" class="button delete-button">刪除</button></view></template></uni-swipe-action-item></uni-swipe-action></view><!-- 購物車空狀態 --><view class="cart-blank" v-else><image src="/static/images/blank_cart.png" class="image" /><text class="text">購物車還是空的,快來挑選好貨吧</text><navigator open-type="switchTab" url="/pages/index/index" hover-class="none"><button class="button">去首頁看看</button></navigator></view><!-- 吸底工具欄 --><view class="toolbar"><text class="all" @tap="onChangeSelectedAll" :class="{ checked: isSelectedAll }">全選</text><text class="text">合計:</text><text class="amount">{{ selectedCartListMoney }}</text><view class="button-grounp"><view@tap="gotoPayment"class="button payment-button":class="{ disabled: selectedCartListCount === 0 }">去結算({{ selectedCartListCount }})</view></view></view></template><!-- 未登錄: 提示登錄 --><view class="login-blank" v-else><text class="text">登錄后可查看購物車中的商品</text><navigator url="/pages/login/login" hover-class="none"><button class="button">去登錄</button></navigator></view><!-- 猜你喜歡 --><Guess ref="guessRef"></XtxGuess><!-- 底部占位空盒子 --><view class="toolbar-height"></view></scroll-view>
</template>