diff --git a/frontend/src/views/user/Cart.vue b/frontend/src/views/user/Cart.vue
index 7bf9194..f8478da 100644
--- a/frontend/src/views/user/Cart.vue
+++ b/frontend/src/views/user/Cart.vue
@@ -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)
+ }"
>
-
+
@@ -64,7 +73,10 @@
+{{ item.product?.credit_reward }}
+
已下架
+ 缺货
+ 资格不足
¥{{ item.product?.price }}
@@ -74,6 +86,7 @@
:min="1"
:max="getMaxQuantity(item.product)"
size="small"
+ :disabled="isOutOfStock(item.product)"
@change="(val: number) => updateQuantity(item, val)"
/>
@@ -92,9 +105,10 @@
:min="1"
:max="getMaxQuantity(item.product)"
size="small"
+ :disabled="isOutOfStock(item.product)"
@change="(val: number) => updateQuantity(item, val)"
/>
-
+
仅剩 {{ item.product?.stock }} 件
@@ -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()
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; }