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

View File

@ -182,7 +182,7 @@ export const setCartTabBadge = () => {
* 验证是否已登录 * 验证是否已登录
*/ */
export const checkLogin = () => { 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 storage from '@/utils/storage'
import Config from '@/core/config' import Config from '@/core/config'
import platform from '@/core/platform' 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() { export default function YoShop2() {
// 当前运行的终端 // 当前运行的终端
store.commit('SET_PLATFORM', platform) store.commit('SET_PLATFORM', platform)
// 用户认证token store.commit('SET_USER_INFO', storage.get('user_info'))
store.commit('SET_TOKEN', storage.get(ACCESS_TOKEN))
// 当前用户ID
store.commit('SET_USER_ID', storage.get(USER_ID))
// 全局自定义主题
store.commit('SET_APP_THEME', storage.get(APP_THEME)) store.commit('SET_APP_THEME', storage.get(APP_THEME))
} }

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
const getters = { const getters = {
platform: state => state.app.platform, platform: state => state.app.platform,
token: state => state.user.token, token: state => state.user.token,
userId: state => state.user.userId, buyerId: state => state.user.buyerId,
appTheme: state => state.theme.appTheme, appTheme: state => state.theme.appTheme,
pageQuery: state => state.page.query 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 storage from '@/utils/storage'
import * as LoginApi from '@/api/login' 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 = { const user = {
state: { state: {
// 用户认证token
token: '', token: '',
// 用户ID buyerId: null,
userId: null,
// 用户标识
flag: null, flag: null,
}, },
mutations: { mutations: {
SET_TOKEN: (state, value) => { SET_USER_INFO: (state, value) => {
state.token = value state.token = value.token
}, state.buyerId = value.buyerId
SET_USER_ID: (state, value) => { state.flag = value.flag
state.userId = value
},
SET_FLAG: (state, value) => {
state.flag = value
} }
}, },
actions: { actions: {
// 用户登录 (普通登录: 输入手机号和验证码) // 用户登录 (普通登录: 输入手机号和验证码)
Login({ commit }, data) { Login({ commit }, data) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
LoginApi.login({ form: data }) LoginApi.login({ form: data })
.then(response => { .then(response => {
const result = response.data const result = response.data
loginSuccess(commit, result) storage.set('user_info', result.buyer)
commit('SET_USER_INFO', result.buyer)
resolve(response) resolve(response)
}) })
.catch(reject) .catch(reject)
@ -60,8 +39,8 @@ const user = {
LoginApi.quickLogin(data) LoginApi.quickLogin(data)
.then(response => { .then(response => {
const result = response.data const result = response.data
uni.setStorageSync('user_info', result.buyer) storage.set('user_info', result.buyer)
loginSuccess(commit, result.buyer) commit('SET_USER_INFO', result.buyer)
resolve(response) resolve(response)
}) })
.catch(reject) .catch(reject)
@ -74,7 +53,8 @@ const user = {
LoginApi.loginMpWxMobile({ form: data }, { isPrompt: true }) LoginApi.loginMpWxMobile({ form: data }, { isPrompt: true })
.then(response => { .then(response => {
const result = response.data const result = response.data
loginSuccess(commit, result) uni.setStorageSync('user_info', result.buyer)
commit('SET_USER_INFO', result.buyer)
resolve(response) resolve(response)
}) })
.catch(reject) .catch(reject)
@ -86,12 +66,8 @@ const user = {
const store = this const store = this
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (store.getters.userId > 0) { if (store.getters.userId > 0) {
// 删除缓存中的tokne和userId storage.remove('user_info')
storage.remove(USER_ID) commit('SET_USER_INFO', { token: '', flag: '', buyerId: '' })
storage.remove(ACCESS_TOKEN)
// 记录到store全局变量
commit('SET_TOKEN', '')
commit('SET_USER_ID', null)
resolve() 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 { export default {
/** /**
* 设置缓存 * 设置缓存
* @param {[type]} k [键名] * @param {[type]} k [键名]
* @param {[type]} v [键值] * @param {[type]} v [键值]
* @param {[type]} t [时间单位秒]
*/ */
set(k, v, t) { set(k, v) {
uni.setStorageSync(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]} k [键名]
* @param {[type]} def [获取为空时默认]
*/ */
get(k, def) { get(k) {
const deadtime = parseInt(uni.getStorageSync(k + postfix)) return uni.getStorageSync(k)
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
}, },
/** /**
@ -72,7 +23,6 @@ export default {
*/ */
remove(k) { remove(k) {
uni.removeStorageSync(k) uni.removeStorageSync(k)
uni.removeStorageSync(k + postfix)
}, },
/** /**