feat: 购物车开发

This commit is contained in:
zc 2025-12-02 23:51:09 +08:00
parent ba07f22b6a
commit 7a63035017
10 changed files with 90 additions and 164 deletions

View File

@ -84,4 +84,12 @@ export const apiActionOrder = (url, data) => {
);
}
// 获取购物车列表
export const apiGetCartList = (data) => {
return httpRequest.post(
`${baseUrl}/shipping/cart/list`,
data
);
}

View File

@ -45,20 +45,16 @@
//
async handle(goods) {
this.goods = goods
if (goods.spec_type == SpecTypeEnum.SINGLE.value) {
this.singleEvent()
}
if (goods.spec_type == SpecTypeEnum.MULTI.value) {
this.multiEvent()
}
this.singleEvent()
// this.multiEvent()
},
//
singleEvent() {
const { goods } = this
this.addCart({
goods_id: goods.goods_id,
goods_sku_id: '0',
goods_id: goods.id,
goods_sku_id: goods.skuId,
buy_num: 1
})
},
@ -153,11 +149,11 @@
//
addCart(selectShop) {
const app = this
const { goods_id, goods_sku_id, buy_num } = selectShop
CartApi.add(goods_id, goods_sku_id, buy_num)
const { goods_sku_id, buy_num } = selectShop
GoodsApi.apiAddCart({ skuId: goods_sku_id, num: buy_num })
.then(result => {
//
app.$toast(result.message, 1000, false)
app.$toast(result.msg, 1000, false)
//
app.onChangeValue(false)
//

View File

@ -182,7 +182,7 @@ export const setCartTabBadge = () => {
* 验证是否已登录
*/
export const checkLogin = () => {
return !!store.getters.userId
return !!store.getters.buyerId
}
/**

8
core/bootstrap.js vendored
View File

@ -2,15 +2,11 @@ import store from '@/store'
import storage from '@/utils/storage'
import Config from '@/core/config'
import platform from '@/core/platform'
import { ACCESS_TOKEN, USER_ID, APP_THEME } from '@/store/mutation-types'
import { APP_THEME } from '@/store/mutation-types'
export default function YoShop2() {
// 当前运行的终端
store.commit('SET_PLATFORM', platform)
// 用户认证token
store.commit('SET_TOKEN', storage.get(ACCESS_TOKEN))
// 当前用户ID
store.commit('SET_USER_ID', storage.get(USER_ID))
// 全局自定义主题
store.commit('SET_USER_INFO', storage.get('user_info'))
store.commit('SET_APP_THEME', storage.get(APP_THEME))
}

View File

@ -23,25 +23,25 @@
<view class="item-radio" @click="handleCheckItem(item.id)">
<u-checkbox :modelValue="inArray(item.id, checkedIds)" shape="circle" :activeColor="appTheme.mainBg" />
</view>
<view class="goods-image" @click="onTargetGoods(item.goods_id)">
<image class="image" :src="item.goods.goods_image" mode="scaleToFill"></image>
<view class="goods-image" @click="onTargetGoods(item.productId)">
<image class="image" :src="item.imageUrl" mode="scaleToFill"></image>
</view>
<view class="item-content">
<view class="goods-title" @click="onTargetGoods(item.goods_id)">
<text class="twoline-hide">{{ item.goods.goods_name }}</text>
<view class="goods-title" @click="onTargetGoods(item.productId)">
<text class="twoline-hide">{{ item.productName }}</text>
</view>
<view class="goods-props clearfix">
<view class="goods-props-item" v-for="(props, idx) in item.goods.skuInfo.goods_props" :key="idx">
<text>{{ props.value.name }}</text>
<view class="goods-props-item" v-for="(props, idx) in item.skuInfo" :key="idx">
<text>{{ props.propertyValue }}</text>
</view>
</view>
<view class="item-foot">
<view class="goods-price">
<text class="unit"></text>
<text class="value">{{ item.goods.skuInfo.goods_price }}</text>
<text class="value">{{ item.singlePrice }}</text>
</view>
<view class="stepper">
<u-number-box :min="1" :modelValue="item.goods_num" :step="1" @change="onChangeStepper($event, item)" />
<u-number-box :min="1" :modelValue="item.num" :step="1" @change="onChangeStepper($event, item)" />
</view>
</view>
</view>
@ -73,7 +73,7 @@
<view class="btn-wrapper">
<!-- dev:下面的disabled条件使用checkedIds.join方式判断 -->
<!-- dev:通常情况下vue项目使用checkedIds.length更合理, 但是length属性在微信小程序中不起作用 -->
<view v-if="mode == 'normal'" class="btn-item btn-main" :class="{ disabled: checkedIds.join() == '' }" @click="handleOrder()">
<view v-if="mode == 'normal'" class="btn-item btn-main" :class="{ disabled: checkedIds.join() == '' }" @click="onSubmit()">
<text>去结算</text>
<!-- <text>去结算 {{ checkedIds.length > 0 ? `(${total})` : '' }}</text> -->
</view>
@ -91,6 +91,8 @@
import { inArray, arrayIntersect, debounce } from '@/utils/util'
import { checkLogin, setCartTotalNum, setCartTabBadge } from '@/core/app'
import * as CartApi from '@/api/cart'
import * as OrderApi from '@/api/order'
import storage from '@/utils/storage'
const CartIdsIndex = 'CartIds'
@ -101,28 +103,19 @@
data() {
return {
inArray,
//
isLoading: true,
// : normal edit
mode: 'normal',
//
mode: 'normal', // : normal edit
list: [],
//
total: null,
// ID
checkedIds: [],
//
totalPrice: '0.00'
}
},
watch: {
//
checkedIds: {
handler(val) {
//
this.onCalcTotalPrice()
//
uni.setStorageSync(CartIdsIndex, val)
this.onCalcTotalPrice() //
uni.setStorageSync(CartIdsIndex, val) //
},
deep: true,
immediate: false
@ -134,10 +127,6 @@
setCartTabBadge()
}
},
/**
* 生命周期函数--监听页面显示
*/
onShow() {
//
this.checkedIds = uni.getStorageSync(CartIdsIndex)
@ -146,7 +135,6 @@
},
methods: {
// ()
onCalcTotalPrice() {
const app = this
@ -156,8 +144,8 @@
let tempPrice = 0;
checkedList.forEach(item => {
// , 便 ()
const unitPrice = item.goods.skuInfo.goods_price * 100
tempPrice += unitPrice * item.goods_num
const unitPrice = item.singlePrice * 100
tempPrice += unitPrice * item.num
})
app.totalPrice = (tempPrice / 100).toFixed(2)
},
@ -166,14 +154,13 @@
getCartList() {
const app = this
app.isLoading = true
CartApi.list()
.then(result => {
app.list = result.data.list
app.total = result.data.cartTotal
OrderApi.apiGetCartList({}).then(res => {
app.total = res.data.total
app.list = res.data.rows.map(item => ({ ...item, skuInfo: JSON.parse(item.skuInfo) }))
// checkedIdsID
app.onClearInvalidId()
})
.finally(() => app.isLoading = false)
})
.finally(() => app.isLoading = false)
},
// checkedIdsID
@ -191,19 +178,20 @@
//
onChangeStepper({ value }, item) {
//
if (item.goods_num == value) return
if (item.num == value) return
//
if (!item.debounceHandle) {
item.oldValue = item.goods_num
item.debounceHandle = debounce(this.onUpdateCartNum, 500)
item.oldValue = item.num
item.num = value
this.onCalcTotalPrice()
// item.debounceHandle = debounce(this.onUpdateCartNum, 500)
}
//
item.goods_num = value
// ()
item.debounceHandle(item, item.oldValue, value)
// item.debounceHandle(item, item.oldValue, value)
},
//
/* // 提交更新购物车数
onUpdateCartNum(item, oldValue, newValue) {
const app = this
CartApi.update(item.goods_id, item.goods_sku_id, newValue)
@ -220,7 +208,7 @@
item.goods_num = oldValue
setTimeout(() => app.$toast(err.errMsg), 10)
})
},
}, */
//
onTargetGoods(goodsId) {
@ -246,11 +234,20 @@
},
//
handleOrder() {
onSubmit() {
const app = this
if (app.checkedIds.length) {
const cartIds = app.checkedIds.join()
app.$navTo('pages/checkout/index', { mode: 'cart', cartIds })
const arr = app.list.filter(item => inArray(item.id, app.checkedIds)).map(item => ({
goodsId: item.productId,
goodsSkuId: item.skuId,
buy_num: item.num,
title: item.productName,
image: item.imageUrl,
price: item.singlePrice,
skuInfo: item.skuInfo
}))
storage.set('goods', arr)
app.$navTo('pages/checkout/index', { mode: 'cart' })
}
},
@ -270,7 +267,6 @@
}
})
},
//
onClearCart() {
const app = this
@ -376,11 +372,14 @@
color: #ababab;
font-size: 24rpx;
overflow: hidden;
display: flex;
align-items: center;
gap: 10rpx;
.goods-props-item {
padding: 4rpx 16rpx;
border-radius: 12rpx;
background-color: #fcfcfc;
background-color: #efefef;
}
}

View File

@ -54,9 +54,9 @@
<text class="goods-name twoline-hide">{{ item.title }}</text>
<!-- 商品规格 -->
<view class="goods-props clearfix">
<view class="goods-props-item" v-for="(props, idx) in item.skuInfo.goods_props" :key="idx">
<text class="group-name">{{ props.group.name }}: </text>
<text>{{ props.value.name }}</text>
<view class="goods-props-item" v-for="(props, idx) in item.skuInfo" :key="idx">
<text class="group-name">{{ props.propertyName }}: </text>
<text>{{ props.propertyValue }}</text>
</view>
</view>
<!-- 商品数量和单价 -->
@ -226,6 +226,7 @@
import * as OrderApi from '@/api/order'
import { CouponTypeEnum } from '@/common/enum/coupon'
import { OrderTypeEnum, DeliveryTypeEnum } from '@/common/enum/order'
import storage from '@/utils/storage'
const CouponColors = ['red', 'blue', 'violet', 'yellow']
@ -309,10 +310,10 @@
*/
onShow() {
//
const goods = JSON.parse(decodeURIComponent(this.options.goods))
const goods = storage.get('goods')
console.warn('----- my data is goods: ', goods)
goods.forEach(item => {
item.skuInfo = { goods_props: [] }
item.skuInfo = item.skuInfo || []
})
this.order.goodsList = goods
// this.getOrderData()

View File

@ -10,6 +10,7 @@
import { hex2rgba } from '@/utils/color'
import * as GoodsApi from '@/api/goods'
import GoodsSkuPopup from '@/components/goods-sku-popup'
import storage from '@/utils/storage'
export default {
components: {
@ -144,9 +145,7 @@
//
buyNow(selectShop) {
//
this.$navTo('pages/checkout/index', {
mode: 'buyNow',
goods: encodeURIComponent(JSON.stringify([{
storage.set('goods', [{
goodsId: selectShop.goods_id,
goodsSkuId: selectShop.goods_sku_id,
goodsNum: selectShop.buy_num,
@ -154,7 +153,8 @@
image: selectShop.image,
price: selectShop.price,
buy_num: selectShop.buy_num
}]))})
}])
this.$navTo('pages/checkout/index', { mode: 'buyNow' })
//
this.onChangeValue(false)
}

View File

@ -1,7 +1,7 @@
const getters = {
platform: state => state.app.platform,
token: state => state.user.token,
userId: state => state.user.userId,
buyerId: state => state.user.buyerId,
appTheme: state => state.theme.appTheme,
pageQuery: state => state.page.query
}

View File

@ -2,51 +2,30 @@ import { ACCESS_TOKEN, USER_ID, FLAG } from '@/store/mutation-types'
import storage from '@/utils/storage'
import * as LoginApi from '@/api/login'
// 登陆成功后执行
const loginSuccess = (commit, { token, buyerId, flag }) => {
// 过期时间30天
const expiryTime = 30 * 86400
// 保存tokne和userId到缓存
storage.set(USER_ID, buyerId, expiryTime)
storage.set(ACCESS_TOKEN, token, expiryTime)
storage.set(FLAG, flag, expiryTime)
// 记录到store全局变量
commit('SET_TOKEN', token)
commit('SET_USER_ID', buyerId)
commit('SET_FLAG', flag)
}
const user = {
state: {
// 用户认证token
token: '',
// 用户ID
userId: null,
// 用户标识
buyerId: null,
flag: null,
},
mutations: {
SET_TOKEN: (state, value) => {
state.token = value
},
SET_USER_ID: (state, value) => {
state.userId = value
},
SET_FLAG: (state, value) => {
state.flag = value
SET_USER_INFO: (state, value) => {
state.token = value.token
state.buyerId = value.buyerId
state.flag = value.flag
}
},
actions: {
// 用户登录 (普通登录: 输入手机号和验证码)
Login({ commit }, data) {
return new Promise((resolve, reject) => {
LoginApi.login({ form: data })
.then(response => {
const result = response.data
loginSuccess(commit, result)
storage.set('user_info', result.buyer)
commit('SET_USER_INFO', result.buyer)
resolve(response)
})
.catch(reject)
@ -60,8 +39,8 @@ const user = {
LoginApi.quickLogin(data)
.then(response => {
const result = response.data
uni.setStorageSync('user_info', result.buyer)
loginSuccess(commit, result.buyer)
storage.set('user_info', result.buyer)
commit('SET_USER_INFO', result.buyer)
resolve(response)
})
.catch(reject)
@ -74,7 +53,8 @@ const user = {
LoginApi.loginMpWxMobile({ form: data }, { isPrompt: true })
.then(response => {
const result = response.data
loginSuccess(commit, result)
uni.setStorageSync('user_info', result.buyer)
commit('SET_USER_INFO', result.buyer)
resolve(response)
})
.catch(reject)
@ -86,12 +66,8 @@ const user = {
const store = this
return new Promise((resolve, reject) => {
if (store.getters.userId > 0) {
// 删除缓存中的tokne和userId
storage.remove(USER_ID)
storage.remove(ACCESS_TOKEN)
// 记录到store全局变量
commit('SET_TOKEN', '')
commit('SET_USER_ID', null)
storage.remove('user_info')
commit('SET_USER_INFO', { token: '', flag: '', buyerId: '' })
resolve()
}
})

View File

@ -1,69 +1,20 @@
/**
* 缓存数据优化
* import storage from '@/utils/storage'
* 使用方法
* 设置缓存
* string storage.set('k', 'string你好啊');
* json storage.set('k', { "b": "3" }, 2);
* array storage.set('k', [1, 2, 3]);
* boolean storage.set('k', true);
* 读取缓存
* 默认值 storage.get('k')
* string storage.get('k', '你好')
* json storage.get('k', { "a": "1" })
* 移除/清理
* 移除: storage.remove('k');
* 清理storage.clear();
*
* @type {String}
*/
const postfix = '_expiry' // 缓存有效期后缀
export default {
/**
* 设置缓存
* @param {[type]} k [键名]
* @param {[type]} v [键值]
* @param {[type]} t [时间单位秒]
*/
set(k, v, t) {
set(k, v) {
uni.setStorageSync(k, v)
const seconds = parseInt(t)
if (seconds > 0) {
let timestamp = Date.parse(new Date())
timestamp = timestamp / 1000 + seconds
uni.setStorageSync(k + postfix, timestamp + '')
} else {
uni.removeStorageSync(k + postfix)
}
},
/**
* 获取缓存
* @param {[type]} k [键名]
* @param {[type]} def [获取为空时默认]
*/
get(k, def) {
const deadtime = parseInt(uni.getStorageSync(k + postfix))
if (deadtime) {
if (parseInt(deadtime) < Date.parse(new Date()) / 1000) {
if (def) {
return def
} else {
return false
}
}
}
const res = uni.getStorageSync(k)
if (res) {
return res
}
if (def == undefined || def == "") {
def = false
}
return def
get(k) {
return uni.getStorageSync(k)
},
/**
@ -72,7 +23,6 @@ export default {
*/
remove(k) {
uni.removeStorageSync(k)
uni.removeStorageSync(k + postfix)
},
/**