fix: 购物车添加库存和资格检查
库存检查: - 库存为0的商品显示"缺货"状态 - 禁止选择缺货商品 - 禁止修改缺货商品数量 - 背景显示红色半透明样式 资格检查: - 获取用户purchase_credits - 检查是否有足够资格购买需要资格的商品 - 资格不足显示"资格不足"状态 - 禁止选择资格不足的商品 结账检查: - 结账前检查选中商品是否都可购买 - 显示不可购买商品的名称 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -26,10 +26,19 @@
|
||||
v-for="item in items"
|
||||
:key="item.id"
|
||||
class="cart-item"
|
||||
:class="{ 'item-disabled': !item.product?.is_active }"
|
||||
:class="{
|
||||
'item-disabled': !item.product?.is_active,
|
||||
'item-out-of-stock': isOutOfStock(item.product),
|
||||
'item-no-credit': !hasEnoughCredit(item.product)
|
||||
}"
|
||||
>
|
||||
<!-- 复选框 -->
|
||||
<el-checkbox v-model="selectedIds" :value="item.id" class="item-check" />
|
||||
<el-checkbox
|
||||
v-model="selectedIds"
|
||||
:value="item.id"
|
||||
class="item-check"
|
||||
:disabled="!canSelectItem(item)"
|
||||
/>
|
||||
|
||||
<!-- 商品图片 -->
|
||||
<div class="item-image-wrap" @click="$router.push(`/products/${item.product?.id}`)">
|
||||
@@ -64,7 +73,10 @@
|
||||
+{{ item.product?.credit_reward }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<!-- 状态提示 -->
|
||||
<p v-if="!item.product?.is_active" class="item-off">已下架</p>
|
||||
<p v-else-if="isOutOfStock(item.product)" class="item-off">缺货</p>
|
||||
<p v-else-if="!hasEnoughCredit(item.product)" class="item-off">资格不足</p>
|
||||
<!-- 底部:价格 + 数量 -->
|
||||
<div class="item-bottom-row">
|
||||
<span class="item-price-val">¥{{ item.product?.price }}</span>
|
||||
@@ -74,6 +86,7 @@
|
||||
:min="1"
|
||||
:max="getMaxQuantity(item.product)"
|
||||
size="small"
|
||||
:disabled="isOutOfStock(item.product)"
|
||||
@change="(val: number) => updateQuantity(item, val)"
|
||||
/>
|
||||
</div>
|
||||
@@ -92,9 +105,10 @@
|
||||
:min="1"
|
||||
:max="getMaxQuantity(item.product)"
|
||||
size="small"
|
||||
:disabled="isOutOfStock(item.product)"
|
||||
@change="(val: number) => updateQuantity(item, val)"
|
||||
/>
|
||||
<span v-if="item.product?.stock !== undefined && item.product?.stock <= 10" class="stock-tip">
|
||||
<span v-if="item.product?.stock !== undefined && item.product?.stock <= 10 && item.product?.stock > 0" class="stock-tip">
|
||||
仅剩 {{ item.product?.stock }} 件
|
||||
</span>
|
||||
</div>
|
||||
@@ -254,7 +268,7 @@ import {
|
||||
Wallet, Coin, ChatDotRound, Delete
|
||||
} from '@element-plus/icons-vue'
|
||||
import { useCartStore } from '../../store/cart'
|
||||
import { systemApi, addressApi, orderApi, paymentChannelApi } from '../../api'
|
||||
import { systemApi, addressApi, orderApi, paymentChannelApi, userApi } from '../../api'
|
||||
import { getFirstImage } from '../../utils/image'
|
||||
import BackNav from '../../components/BackNav.vue'
|
||||
|
||||
@@ -269,6 +283,7 @@ const selectedAddressId = ref<number>()
|
||||
const selectedPayment = ref('')
|
||||
const showAddAddress = ref(false)
|
||||
const submitting = ref(false)
|
||||
const userCredits = ref(0)
|
||||
|
||||
const addressForm = reactive({
|
||||
name: '',
|
||||
@@ -354,6 +369,13 @@ async function fetchSettings() {
|
||||
} catch {}
|
||||
}
|
||||
|
||||
async function fetchUserProfile() {
|
||||
try {
|
||||
const res: any = await userApi.getProfile()
|
||||
userCredits.value = res.data?.purchase_credits || 0
|
||||
} catch {}
|
||||
}
|
||||
|
||||
async function fetchPaymentChannels() {
|
||||
try {
|
||||
const res: any = await paymentChannelApi.list()
|
||||
@@ -393,14 +415,35 @@ async function addAddress() {
|
||||
|
||||
function handleSelectAll(val: boolean) {
|
||||
if (val) {
|
||||
selectedIds.value = items.value.filter(i => i.product?.is_active).map(i => i.id)
|
||||
selectedIds.value = items.value.filter(i => canSelectItem(i)).map(i => i.id)
|
||||
} else {
|
||||
selectedIds.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 检查商品是否缺货
|
||||
function isOutOfStock(product: any): boolean {
|
||||
if (!product) return false
|
||||
return product.stock !== undefined && product.stock !== null && product.stock <= 0
|
||||
}
|
||||
|
||||
// 检查用户是否有足够资格购买
|
||||
function hasEnoughCredit(product: any): boolean {
|
||||
if (!product || !product.require_credit) return true
|
||||
return userCredits.value >= (product.credit_cost || 0)
|
||||
}
|
||||
|
||||
// 检查商品是否可以选择
|
||||
function canSelectItem(item: any): boolean {
|
||||
if (!item.product?.is_active) return false
|
||||
if (isOutOfStock(item.product)) return false
|
||||
if (!hasEnoughCredit(item.product)) return false
|
||||
return true
|
||||
}
|
||||
|
||||
function getMaxQuantity(product: any) {
|
||||
if (!product) return 99
|
||||
if (isOutOfStock(product)) return 0
|
||||
let max = 999
|
||||
if (product.stock !== undefined && product.stock !== null) {
|
||||
max = product.stock
|
||||
@@ -442,18 +485,20 @@ async function handleCheckout() {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const unavailableItems = items.value
|
||||
.filter(item => selectedIds.value.includes(item.id) && !item.product?.is_active)
|
||||
|
||||
|
||||
// 检查选中商品是否有不可购买的
|
||||
const selectedItems = items.value.filter(item => selectedIds.value.includes(item.id))
|
||||
const unavailableItems = selectedItems.filter(item => !canSelectItem(item))
|
||||
|
||||
if (unavailableItems.length > 0) {
|
||||
ElMessage.warning('请移除已下架的商品')
|
||||
const names = unavailableItems.map(item => item.product?.name).join('、')
|
||||
ElMessage.warning(`以下商品不可购买:${names}`)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
submitting.value = true
|
||||
try {
|
||||
const res: any = await orderApi.create({
|
||||
const res: any = await orderApi.create({
|
||||
shipping_address_id: selectedAddressId.value,
|
||||
payment_method: selectedPayment.value,
|
||||
cart_item_ids: selectedIds.value
|
||||
@@ -483,6 +528,7 @@ watch(items, () => {
|
||||
onMounted(() => {
|
||||
cartStore.fetchCart()
|
||||
fetchSettings()
|
||||
fetchUserProfile()
|
||||
fetchAddresses()
|
||||
fetchPaymentChannels()
|
||||
})
|
||||
@@ -565,6 +611,16 @@ onMounted(() => {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&.item-out-of-stock {
|
||||
opacity: 0.6;
|
||||
background: rgba(245, 108, 108, 0.1) !important;
|
||||
}
|
||||
|
||||
&.item-no-credit {
|
||||
opacity: 0.6;
|
||||
background: rgba(245, 158, 11, 0.1) !important;
|
||||
}
|
||||
|
||||
/* 桌面端布局 */
|
||||
> .item-check { width: 60px; flex-shrink: 0; }
|
||||
> .item-image-wrap { width: 70px; flex-shrink: 0; }
|
||||
|
||||
Reference in New Issue
Block a user