fix(预售加购): 预售/自定义表单商品隐藏加购并拦截加入购物车

列表补齐 cart_button,详情从 SKU 回填最小有效会员价供展示。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
吴昊天 2026-10-26 12:04:40 +08:00
parent 300b1d8677
commit e87a6b20be
4 changed files with 102 additions and 55 deletions

View File

@ -339,6 +339,10 @@ class StoreCartServices extends BaseService
if (!$new && $this->isDirectCartMode($product_id)) {
throw new ApiException('该商品不支持加入购物车,请直接购买');
}
// 预售商品走预售下单流程,禁止加入购物车(立即购买 new=true 不受限)
if (!$new && $this->isPresaleProduct($product_id)) {
throw new ApiException('预售商品不支持加入购物车,请直接购买');
}
if ($type == 0) {
//检查限购
$this->checkLimit($uid, $product_id, $cart_num, $new);
@ -453,6 +457,20 @@ class StoreCartServices extends BaseService
}
}
/**
* 判断是否为预售商品(开启预售后只能走预售购买,不能进购物车)
* @param int $productId 商品ID
* @return bool
*/
protected function isPresaleProduct(int $productId): bool
{
try {
return (int)app()->make(CenterProductServices::class)->value(['id' => $productId], 'presale') === 1;
} catch (\Throwable $e) {
return false;
}
}
/**移除购物车商品
* @param int $uid
* @param array $ids
@ -612,6 +630,10 @@ class StoreCartServices extends BaseService
if (!$productServices->isValidProduct((int)$productId, 'id')) {
throw new ApiException('该商品已下架或删除');
}
// 列表加购入口:预售商品禁止写入购物车
if ($this->isPresaleProduct((int)$productId)) {
throw new ApiException('预售商品不支持加入购物车,请直接购买');
}
// 通过 ProductTypeHandler 获取默认 SKU 并校验有效性
$skuInfo = $this->getProductSkuInfo((int)$productId, $unique);

View File

@ -780,6 +780,19 @@ class CenterProductServices extends BaseService
$svipOpen = $memberCardService->isOpenMemberCard('vip_price');
} catch (\Throwable $e) {}
$svipPriceOpen = (int)sys_config('member_price_status');
// 商品主表 vip_price 可能为 0(会员价只写在 SKU),从规格中取最小有效会员价供详情展示
if ((float)($storeInfo['vip_price'] ?? 0) <= 0 && !empty($productValue)) {
$skuVipPrices = [];
foreach ($productValue as $skuRow) {
$skuVip = (float)($skuRow['vip_price'] ?? 0);
if ($skuVip > 0) {
$skuVipPrices[] = $skuVip;
}
}
if ($skuVipPrices) {
$storeInfo['vip_price'] = (string)min($skuVipPrices);
}
}
$storeInfo['svip_economize_price'] = bcsub((string)$storeInfo['price'], (string)$storeInfo['vip_price'], 2);
if (!($svipOpen && sys_config('member_card_status') && $storeInfo['is_vip'] && sys_config('member_price_status', 1))) {
$storeInfo['vip_price'] = 0;
@ -931,9 +944,8 @@ class CenterProductServices extends BaseService
$storeInfo['cart_strategy']['cart_mode'] = 'direct';
}
// cart_button 由 cart_strategy.cart_mode 决定:direct 模式不展示加入购物车按钮
// 预售商品在模板中走独立分支,不影响此处判断
$storeInfo['cart_button'] = ($storeInfo['cart_strategy']['cart_mode'] ?? 'cart') === 'direct' ? 0 : 1;
// 预售 / 自定义表单 / direct 模式均不展示加入购物车
$storeInfo['cart_button'] = $this->canShowCartButton($storeInfo) ? 1 : 0;
// ========== 14. 组装扁平返回数据 ==========
$result = [
@ -1088,9 +1100,12 @@ class CenterProductServices extends BaseService
'bottom_bar' => [
'text' => '立即购买',
'action' => 'submitOrder',
// cart_button 由 cart_strategy.cart_mode 决定:direct 模式不展示加入购物车按钮
// 自定义表单商品需填表后直接下单,同样不展示加入购物车
'extra' => ['cart_button' => !$this->hasCustomForm($extensions['custom_form'] ?? []) && $this->getCartModeByType($productType) !== 'direct'],
// 预售 / 自定义表单 / direct 模式均不展示加入购物车
'extra' => ['cart_button' => $this->canShowCartButton([
'presale' => $data['product']['presale'] ?? 0,
'custom_form' => $extensions['custom_form'] ?? [],
'product_type' => $productType,
])],
],
'submit_api' => 'center/order/create',
];
@ -1595,6 +1610,26 @@ class CenterProductServices extends BaseService
return trim((string)$customForm) !== '';
}
/**
* 是否允许展示/使用加入购物车
* 预售、自定义表单、direct 加购模式均不允许进购物车,应进详情直接购买
* @param array $item 商品信息(需含 presale / custom_form / product_type)
* @return bool
*/
protected function canShowCartButton(array $item): bool
{
if ((int)($item['presale'] ?? 0) === 1) {
return false;
}
if ($this->hasCustomForm($item['custom_form'] ?? '')) {
return false;
}
if ($this->getCartModeByType((string)($item['product_type'] ?? 'normal')) === 'direct') {
return false;
}
return true;
}
/**
* 获取产品类型展示名称
* 通过 ProductTypeRegistry 咨询对应 Handler
@ -2458,8 +2493,8 @@ class CenterProductServices extends BaseService
if (!$this->vipIsOpen(!!$item['is_vip'], $vipStatus)) {
$item['vip_price'] = 0;
}
// 自定义表单商品不支持加入购物车(前端据此点击直接进详情)
$item['cart_button'] = $this->hasCustomForm($item['custom_form'] ?? '') || $this->getCartModeByType($item['product_type'] ?? 'normal') === 'direct' ? 0 : 1;
// 预售 / 自定义表单 / direct 模式不支持加入购物车(前端据此进详情)
$item['cart_button'] = $this->canShowCartButton($item) ? 1 : 0;
if (count($item['star'])) {
$item['star'] = bcdiv((string)array_sum(array_column($item['star'], 'product_score')), (string)count($item['star']), 1);
} else {
@ -2641,10 +2676,13 @@ class CenterProductServices extends BaseService
if (!array_key_exists('vip_user', $where)) {
$where['vip_user'] = $this->resolveVipUserLevel($uid);
}
$list = $this->dao->getSearchList($where, $page, $limit, ['id,name as store_name,cate_id,image,IFNULL(sales, 0) + IFNULL(virtual_sales, 0) as sales,price,stock,unit_name,presale,is_vip,vip_price']);
$list = $this->dao->getSearchList($where, $page, $limit, ['id,name as store_name,cate_id,image,IFNULL(sales, 0) + IFNULL(virtual_sales, 0) as sales,price,stock,unit_name,presale,custom_form,product_type,is_vip,vip_price']);
// eb_product_center 无 activity 列,补默认值兼容 getActivityList
foreach ($list as &$item) {
$item['activity'] = $item['activity'] ?? '';
$item['presale'] = (int)($item['presale'] ?? 0);
// 预售 / 自定义表单 / direct 模式不支持加入购物车
$item['cart_button'] = $this->canShowCartButton($item) ? 1 : 0;
}
$list = $this->getActivityList($list);
return $list;
@ -2742,9 +2780,8 @@ class CenterProductServices extends BaseService
}
$storeInfo['cart_strategy'] = $cartStrategy;
// cart_button 由 cart_strategy.cart_mode 决定:direct 模式不展示加入购物车按钮
// 预售商品在模板中走独立分支,不影响此处判断
$storeInfo['cart_button'] = ($cartStrategy['cart_mode'] ?? 'cart') === 'direct' ? 0 : 1;
// 预售 / 自定义表单 / direct 模式均不展示加入购物车
$storeInfo['cart_button'] = $this->canShowCartButton($storeInfo) ? 1 : 0;
$specs = $handler->getDetailSpecs($id);
$productAttr = $specs['productAttr'] ?? [];
@ -3988,6 +4025,7 @@ class CenterProductServices extends BaseService
'is_vip',
'vip_price',
'custom_form',
'presale',
];
$list = $this->dao->getSearchList($where, $page, $limit, $field);
@ -3997,13 +4035,13 @@ class CenterProductServices extends BaseService
$item['activity'] = '';
$item['vip_price'] = '0.00';
$item['is_virtual'] = $this->isVirtualProduct($item['product_type'] ?? 'normal');
$item['presale'] = 0;
$item['presale'] = (int)($item['presale'] ?? 0);
$item['min_qty'] = 1;
$item['label_list'] = '';
$item['store_info'] = '';
$item['star'] = '5.0';
// 自定义表单商品不支持加入购物车(前端据此点击直接进详情)
$item['cart_button'] = $this->hasCustomForm($item['custom_form'] ?? '') || $this->getCartModeByType($item['product_type'] ?? 'normal') === 'direct' ? 0 : 1;
// 预售 / 自定义表单 / direct 模式不支持加入购物车(前端据此进详情)
$item['cart_button'] = $this->canShowCartButton($item) ? 1 : 0;
// 标记活动商品
if (!empty($item['activity'])) {

View File

@ -23,12 +23,15 @@
</view>
<view class="flex-between-center mt-12">
<text class="fs-22 text--w111-999">{{ $t(`已售`) }}{{item.sales}}{{item.unit_name}}</text>
<view class="w-44 h-44 rd-24 bg-gradient flex-center" v-if="goDetail=='goDetail'">
<text class="iconfont icon-ic_ShoppingCart1 text--w111-fff fs-26"></text>
</view>
<view class="w-44 h-44 rd-24 bg-gradient flex-center" @tap.stop="addCartChange" v-else>
<text class="iconfont icon-ic_ShoppingCart1 text--w111-fff fs-26"></text>
</view>
<!-- 预售等 cart_button=0 的商品列表不展示加购入口 -->
<template v-if="item.cart_button != 0">
<view class="w-44 h-44 rd-24 bg-gradient flex-center" v-if="goDetail=='goDetail'">
<text class="iconfont icon-ic_ShoppingCart1 text--w111-fff fs-26"></text>
</view>
<view class="w-44 h-44 rd-24 bg-gradient flex-center" @tap.stop="addCartChange" v-else>
<text class="iconfont icon-ic_ShoppingCart1 text--w111-fff fs-26"></text>
</view>
</template>
</view>
</view>
</view>

View File

@ -1570,8 +1570,6 @@ function ChangeCartNum(changeValue, num, stock, isDuo, id, index, cart) {
if (!isDuo) {
if (cart) {
goCat(0, id, 1, 1, num.product_attr_unique);
// 遗留 bug:原 mixin 调用 this.getTotalPrice(),本组件从未定义该方法(运行时必报错),保持原语义
getTotalPrice();
} else {
goCat(0, id, 1);
}
@ -1599,7 +1597,6 @@ function ChangeCartNum(changeValue, num, stock, isDuo, id, index, cart) {
if (!isDuo) {
if (cart) {
goCat(0, id, 0, 1, num.product_attr_unique);
getTotalPrice();
} else {
goCat(0, id, 0);
}
@ -1653,18 +1650,15 @@ function goCat(duo, goodsId, type, cart, unique) {
}
util.Tips({
title: res.msg
title: res.msg || '加入购物车成功'
});
getCartNum();
if (!cart) {
// 遗留 bug:原 mixin 调用 this.getCartList(1),本组件从未定义该方法(运行时必报错),保持原语义
getCartList(1);
}
// DIY 商品列表无分类页悬浮购物车,不调用 getCartList
})
.catch(err => {
return util.Tips({
title: err
title: (err && (err.msg || err.message)) || err
});
});
}
@ -1672,20 +1666,15 @@ function goCartDuo(item, num) {
if (!isLogin.value) {
toLogin();
} else {
if(item.cart_button == 0){
if(item.is_presale_product){
uni.navigateTo({
url: `/pages/activity/goods_details/index?id=${item.id}&type=6`
})
}else{
//num:表示从首页点击进来的
let page1 = `/pages/goods_details/index?id=${item.id}&fromType=1`;
let page2 = `/pages/goods_details/index?id=${item.id}`;
uni.navigateTo({
url: num?page2:page1
})
}
}else{
// 预售 / 自定义表单 / direct 模式:列表不支持加购,进详情购买
if (item.cart_button == 0) {
//num:表示从首页点击进来的
let page1 = `/pages/goods_details/index?id=${item.id}&fromType=1`;
let page2 = `/pages/goods_details/index?id=${item.id}`;
uni.navigateTo({
url: num ? page2 : page1
});
} else {
storeName.value = item.store_name;
getAttrs(item.id);
id.value = item.id;
@ -1698,17 +1687,12 @@ function goCartDan(item, index, num) {
if (!isLogin.value) {
toLogin();
} else {
if(item.cart_button == 0){
if(item.is_presale_product){
uni.navigateTo({
url: `/pages/activity/goods_details/index?id=${item.id}&type=6`
})
}else{
uni.navigateTo({
url: `/pages/goods_details/index?id=${item.id}&fromType=1`
})
}
}else{
// 预售 / 自定义表单 / direct 模式:列表不支持加购,进详情购买
if (item.cart_button == 0) {
uni.navigateTo({
url: `/pages/goods_details/index?id=${item.id}&fromType=1`
});
} else {
tempArr.value[index].cart_num = 1;
// num表示从门店过来的;
// if(num){