Files
sale/frontend/src/views/user/Cart.vue
T
admin c6400f5f67 fix: 优化删除按钮和数量选择器样式
删除按钮:
- 改用垃圾桶图标,更直观美观
- 圆角矩形背景,hover效果
- 红色主题,符合删除语义

数量选择器:
- 增加宽度至110px
- 调整padding确保数字居中
- 加减按钮宽度32px,易于点击
- 数字居中显示,不再重叠

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 21:40:13 +08:00

1289 lines
32 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="cart-page">
<BackNav />
<el-empty v-if="!items.length" description="购物车是空的" :image-size="120">
<template #image>
<el-icon :size="80" color="rgba(255,255,255,0.2)"><ShoppingCart /></el-icon>
</template>
<el-button type="primary" @click="$router.push('/products')">去购物</el-button>
</el-empty>
<div v-else class="cart-container">
<div class="cart-left">
<div class="cart-main">
<div class="cart-header">
<el-checkbox v-model="selectAll" @change="handleSelectAll">全选</el-checkbox>
<span class="header-product">商品信息</span>
<span class="header-price">单价</span>
<span class="header-quantity">数量</span>
<span class="header-subtotal">小计</span>
<span class="header-action">操作</span>
</div>
<div class="cart-list">
<div
v-for="item in items"
:key="item.id"
class="cart-item"
:class="{ 'item-disabled': !item.product?.is_active }"
>
<!-- 复选框 -->
<el-checkbox v-model="selectedIds" :value="item.id" class="item-check" />
<!-- 商品图片 -->
<div class="item-image-wrap" @click="$router.push(`/products/${item.product?.id}`)">
<el-image
:src="getFirstImage(item.product?.images)"
fit="cover"
class="item-image"
>
<template #error>
<div class="image-placeholder">
<el-icon><Picture /></el-icon>
</div>
</template>
</el-image>
</div>
<!-- 商品信息区 -->
<div class="item-content">
<!-- 标题行商品名 + 删除按钮 -->
<div class="item-title-row">
<h4 class="item-name" @click="$router.push(`/products/${item.product?.id}`)">{{ item.product?.name }}</h4>
<span class="btn-delete" @click.stop="removeItem(item.id)">
<el-icon><Delete /></el-icon>
</span>
</div>
<!-- 标签 -->
<div class="item-tags" v-if="item.product?.require_credit || item.product?.credit_reward > 0">
<el-tag v-if="item.product?.require_credit" size="small" type="warning">
需要: {{ item.product?.credit_cost }}
</el-tag>
<el-tag v-if="item.product?.credit_reward > 0" size="small" type="success">
+{{ item.product?.credit_reward }}
</el-tag>
</div>
<p v-if="!item.product?.is_active" class="item-off">已下架</p>
<!-- 底部价格 + 数量 -->
<div class="item-bottom-row">
<span class="item-price-val">¥{{ item.product?.price }}</span>
<div class="quantity-wrap">
<el-input-number
v-model="item.quantity"
:min="1"
:max="getMaxQuantity(item.product)"
size="small"
@change="(val: number) => updateQuantity(item, val)"
/>
</div>
</div>
</div>
<!-- 桌面端单价 -->
<div class="item-price">
<span class="price-current">¥{{ item.product?.price }}</span>
</div>
<!-- 桌面端数量 -->
<div class="item-quantity">
<el-input-number
v-model="item.quantity"
:min="1"
:max="getMaxQuantity(item.product)"
size="small"
@change="(val: number) => updateQuantity(item, val)"
/>
<span v-if="item.product?.stock !== undefined && item.product?.stock <= 10" class="stock-tip">
仅剩 {{ item.product?.stock }}
</span>
</div>
<!-- 桌面端小计 -->
<div class="item-subtotal">
<span class="subtotal-price">¥{{ ((item.product?.price || 0) * item.quantity).toFixed(2) }}</span>
</div>
<!-- 桌面端删除 -->
<div class="item-action">
<el-button link type="danger" size="small" @click="removeItem(item.id)">删除</el-button>
</div>
</div>
</div>
</div>
<div class="checkout-section">
<h3 class="section-title">
<el-icon><Location /></el-icon>
收货地址
</h3>
<div class="address-list" v-if="addresses.length">
<div
v-for="addr in addresses"
:key="addr.id"
class="address-item"
:class="{ active: selectedAddressId === addr.id }"
@click="selectedAddressId = addr.id"
>
<div class="address-radio">
<el-icon v-if="selectedAddressId === addr.id" class="checked"><CircleCheckFilled /></el-icon>
<span v-else class="unchecked"></span>
</div>
<div class="address-content">
<div class="address-header">
<span class="address-name">{{ addr.name }}</span>
<span class="address-phone">{{ addr.phone }}</span>
<el-tag v-if="addr.is_default" size="small" type="success">默认</el-tag>
</div>
<div class="address-detail">
{{ addr.province }}{{ addr.city }}{{ addr.district }}{{ addr.address }}
</div>
</div>
</div>
</div>
<div class="no-address" v-else>
<p>您还没有添加收货地址</p>
<el-button type="primary" @click="showAddAddress = true">添加地址</el-button>
</div>
<el-button class="add-address-btn" link type="primary" @click="showAddAddress = true" v-if="addresses.length">
<el-icon><Plus /></el-icon>
添加新地址
</el-button>
</div>
<div class="checkout-section" v-if="availablePayments.length > 0">
<h3 class="section-title">
<el-icon><CreditCard /></el-icon>
支付方式
</h3>
<div class="payment-methods" :class="{ 'single': availablePayments.length === 1 }">
<div
v-for="method in availablePayments"
:key="method.value"
class="payment-item"
:class="{ active: selectedPayment === method.value }"
@click="selectedPayment = method.value"
>
<el-icon class="payment-icon"><component :is="method.icon" /></el-icon>
<span class="payment-name">{{ method.label }}</span>
<el-icon v-if="selectedPayment === method.value" class="check-icon"><CircleCheckFilled /></el-icon>
</div>
</div>
</div>
</div>
<div class="cart-summary-panel">
<h3 class="summary-title">订单摘要</h3>
<div class="summary-row">
<span>商品金额</span>
<span>¥{{ selectedTotal.toFixed(2) }}</span>
</div>
<div class="summary-row">
<span>运费</span>
<span v-if="shippingFee > 0">¥{{ shippingFee.toFixed(2) }}</span>
<span v-else class="free-shipping">免运费</span>
</div>
<div class="summary-row" v-if="serviceFee > 0">
<span>服务费 ({{ settings.service_fee_rate || 0 }}%)</span>
<span>¥{{ serviceFee.toFixed(2) }}</span>
</div>
<div class="summary-row" v-if="taxFee > 0">
<span>税费 ({{ settings.tax_rate || 0 }}%)</span>
<span>¥{{ taxFee.toFixed(2) }}</span>
</div>
<div class="summary-divider"></div>
<div class="summary-row summary-total">
<span>应付总额</span>
<span class="total-amount">¥{{ grandTotal.toFixed(2) }}</span>
</div>
<div class="summary-count">
已选择 <strong>{{ selectedIds.length }}</strong> 件商品
</div>
<el-button
type="primary"
size="large"
class="checkout-btn"
:disabled="!canCheckout"
:loading="submitting"
@click="handleCheckout"
>
提交订单
</el-button>
<div class="summary-actions">
<el-checkbox v-model="selectAll" @change="handleSelectAll">全选</el-checkbox>
<el-button link type="danger" size="small" @click="removeSelected" :disabled="!selectedIds.length">
删除选中
</el-button>
</div>
</div>
</div>
<el-dialog v-model="showAddAddress" title="添加收货地址" width="500px">
<el-form :model="addressForm" label-width="100px">
<el-form-item label="收货人" required><el-input v-model="addressForm.name" placeholder="请输入收货人姓名" /></el-form-item>
<el-form-item label="手机号" required><el-input v-model="addressForm.phone" placeholder="请输入手机号" /></el-form-item>
<el-form-item label="省份"><el-input v-model="addressForm.province" placeholder="省/直辖市" /></el-form-item>
<el-form-item label="城市"><el-input v-model="addressForm.city" placeholder="市/区" /></el-form-item>
<el-form-item label="区县"><el-input v-model="addressForm.district" placeholder="区/县" /></el-form-item>
<el-form-item label="详细地址" required><el-input v-model="addressForm.address" type="textarea" :rows="2" placeholder="街道、门牌号等" /></el-form-item>
<el-form-item><el-switch v-model="addressForm.is_default" active-text="设为默认地址" /></el-form-item>
</el-form>
<template #footer>
<el-button @click="showAddAddress = false">取消</el-button>
<el-button type="primary" @click="addAddress">保存</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed, watch, onMounted, markRaw } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
ShoppingCart, Picture, Location, CreditCard, Plus, CircleCheckFilled,
Wallet, Coin, ChatDotRound, Delete
} from '@element-plus/icons-vue'
import { useCartStore } from '../../store/cart'
import { systemApi, addressApi, orderApi, paymentChannelApi } from '../../api'
import { getFirstImage } from '../../utils/image'
import BackNav from '../../components/BackNav.vue'
const router = useRouter()
const cartStore = useCartStore()
const items = computed(() => cartStore.items)
const selectedIds = ref<number[]>([])
const settings = ref<Record<string, string>>({})
const addresses = ref<any[]>([])
const selectedAddressId = ref<number>()
const selectedPayment = ref('')
const showAddAddress = ref(false)
const submitting = ref(false)
const addressForm = reactive({
name: '',
phone: '',
province: '',
city: '',
district: '',
address: '',
is_default: false
})
const typeIconMap: Record<string, any> = {
balance: markRaw(Wallet),
alipay: markRaw(Coin),
wechat: markRaw(ChatDotRound),
bepusdt: markRaw(Coin),
bank: markRaw(CreditCard),
other: markRaw(Coin),
}
const paymentChannels = ref<any[]>([])
const availablePayments = computed(() => {
return paymentChannels.value.map(ch => ({
value: ch.code,
label: ch.name,
icon: typeIconMap[ch.type] || markRaw(Coin),
}))
})
const selectAll = computed({
get: () => {
const activeItems = items.value.filter(i => i.product?.is_active)
return activeItems.length > 0 && selectedIds.value.length === activeItems.length
},
set: () => {}
})
const selectedTotal = computed(() => {
return items.value
.filter(item => selectedIds.value.includes(item.id))
.reduce((sum, item) => sum + (item.product?.price || 0) * item.quantity, 0)
})
const selectedQuantity = computed(() => {
return items.value
.filter(item => selectedIds.value.includes(item.id))
.reduce((sum, item) => sum + item.quantity, 0)
})
const shippingFee = computed(() => {
const firstWeight = parseFloat(settings.value.shipping_fee_first_weight || '0')
const perGram = parseFloat(settings.value.shipping_fee_per_gram || '0')
if (selectedTotal.value >= 99 || firstWeight === 0) return 0
return firstWeight + perGram * Math.max(0, selectedQuantity.value - 500)
})
const serviceFee = computed(() => {
const rate = parseFloat(settings.value.service_fee_rate || '0')
return selectedTotal.value * rate / 100
})
const taxFee = computed(() => {
const rate = parseFloat(settings.value.tax_rate || '0')
return selectedTotal.value * rate / 100
})
const grandTotal = computed(() => {
return selectedTotal.value + shippingFee.value + serviceFee.value + taxFee.value
})
const canCheckout = computed(() => {
if (selectedIds.value.length === 0) return false
if (!selectedAddressId.value) return false
const selectedItems = items.value.filter(item => selectedIds.value.includes(item.id))
return selectedItems.every(item => item.product?.is_active)
})
async function fetchSettings() {
try {
const res: any = await systemApi.getPublicSettings()
settings.value = res.data || {}
} catch {}
}
async function fetchPaymentChannels() {
try {
const res: any = await paymentChannelApi.list()
paymentChannels.value = res.data || []
if (paymentChannels.value.length > 0 && !selectedPayment.value) {
selectedPayment.value = paymentChannels.value[0].code
}
} catch {}
}
async function fetchAddresses() {
try {
const res: any = await addressApi.list()
addresses.value = res.data || []
const defaultAddr = addresses.value.find((a: any) => a.is_default)
if (defaultAddr && !selectedAddressId.value) {
selectedAddressId.value = defaultAddr.id
} else if (addresses.value.length > 0 && !selectedAddressId.value) {
selectedAddressId.value = addresses.value[0].id
}
} catch {}
}
async function addAddress() {
if (!addressForm.name || !addressForm.phone || !addressForm.address) {
ElMessage.warning('请填写必要信息')
return
}
try {
await addressApi.create(addressForm)
ElMessage.success('地址已添加')
showAddAddress.value = false
Object.assign(addressForm, { name: '', phone: '', province: '', city: '', district: '', address: '', is_default: false })
await fetchAddresses()
} catch {}
}
function handleSelectAll(val: boolean) {
if (val) {
selectedIds.value = items.value.filter(i => i.product?.is_active).map(i => i.id)
} else {
selectedIds.value = []
}
}
function getMaxQuantity(product: any) {
if (!product) return 99
let max = 999
if (product.stock !== undefined && product.stock !== null) {
max = product.stock
}
if (product.max_purchase && product.max_purchase > 0) {
max = Math.min(max, product.max_purchase)
}
return Math.max(1, max)
}
async function updateQuantity(item: any, quantity: number) {
if (quantity < 1) return
await cartStore.updateItem(item.id, quantity)
}
async function removeItem(id: number) {
try {
await ElMessageBox.confirm('确定要删除该商品吗?', '提示', { type: 'warning' })
await cartStore.removeItem(id)
selectedIds.value = selectedIds.value.filter(i => i !== id)
ElMessage.success('已删除')
} catch {}
}
async function removeSelected() {
if (!selectedIds.value.length) return
try {
await ElMessageBox.confirm(`确定要删除选中的 ${selectedIds.value.length} 件商品吗?`, '提示', { type: 'warning' })
await cartStore.removeItems(selectedIds.value)
selectedIds.value = []
ElMessage.success('已删除')
} catch {}
}
async function handleCheckout() {
if (!canCheckout.value) {
if (!selectedAddressId.value) {
ElMessage.warning('请选择收货地址')
}
return
}
const unavailableItems = items.value
.filter(item => selectedIds.value.includes(item.id) && !item.product?.is_active)
if (unavailableItems.length > 0) {
ElMessage.warning('请移除已下架的商品')
return
}
submitting.value = true
try {
const res: any = await orderApi.create({
shipping_address_id: selectedAddressId.value,
payment_method: selectedPayment.value,
cart_item_ids: selectedIds.value
})
ElMessage.success('订单创建成功')
await cartStore.fetchCart()
const orderId = res.data?.id
const selectedChannel = paymentChannels.value.find(ch => ch.code === selectedPayment.value)
if (selectedChannel?.type === 'bepusdt' && orderId) {
router.push(`/checkout/${orderId}`)
} else {
router.push('/orders')
}
} catch (err: any) {
ElMessage.error(err.response?.data?.error || '订单创建失败')
} finally {
submitting.value = false
}
}
watch(items, () => {
selectedIds.value = selectedIds.value.filter(id =>
items.value.some(item => item.id === id)
)
}, { deep: true })
onMounted(() => {
cartStore.fetchCart()
fetchSettings()
fetchAddresses()
fetchPaymentChannels()
})
</script>
<style scoped lang="scss">
.cart-page {
padding-bottom: 40px;
}
.cart-container {
display: grid;
grid-template-columns: 1fr 360px;
gap: 24px;
align-items: start;
}
.cart-left {
display: flex;
flex-direction: column;
gap: 20px;
}
.cart-main {
background: #2d2d44;
border: 1px solid rgba(255, 255, 255, 0.06);
border-radius: 12px;
overflow: hidden;
}
.cart-header {
display: flex;
align-items: center;
padding: 14px 20px;
background: rgba(255, 255, 255, 0.02);
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
font-size: 13px;
color: rgba(255, 255, 255, 0.5);
:deep(.el-checkbox) {
width: 60px;
flex-shrink: 0;
margin-right: 0;
.el-checkbox__label {
font-size: 13px;
padding-left: 8px;
}
}
.header-product { flex: 1; min-width: 200px; }
.header-price { width: 100px; text-align: center; flex-shrink: 0; }
.header-quantity { width: 140px; text-align: center; flex-shrink: 0; }
.header-subtotal { width: 100px; text-align: center; flex-shrink: 0; }
.header-action { width: 60px; text-align: center; flex-shrink: 0; }
}
.cart-list {
max-height: 400px;
overflow-y: auto;
}
.cart-item {
display: flex;
align-items: center;
padding: 16px 20px;
border-bottom: 1px solid rgba(255, 255, 255, 0.04);
transition: background 0.2s;
position: relative;
&:hover {
background: rgba(255, 255, 255, 0.02);
}
&:last-child {
border-bottom: none;
}
&.item-disabled {
opacity: 0.5;
}
/* 桌面端布局 */
> .item-check { width: 60px; flex-shrink: 0; }
> .item-image-wrap { width: 70px; flex-shrink: 0; }
> .item-content { flex: 1; min-width: 200px; }
> .item-price { width: 100px; text-align: center; flex-shrink: 0; }
> .item-quantity { width: 140px; display: flex; flex-direction: column; align-items: center; gap: 4px; flex-shrink: 0; }
> .item-subtotal { width: 100px; text-align: center; flex-shrink: 0; }
> .item-action { width: 60px; display: flex; justify-content: center; flex-shrink: 0; }
}
.item-product {
display: flex;
gap: 12px;
cursor: pointer;
flex-wrap: wrap;
align-items: flex-start;
}
.item-image {
width: 70px;
height: 70px;
border-radius: 8px;
flex-shrink: 0;
background: rgba(255, 255, 255, 0.06);
overflow: hidden;
}
/* 桌面端图片和信息布局 */
.item-image-wrap {
margin-right: 12px;
cursor: pointer;
display: block;
}
.item-content {
display: flex;
flex-direction: column;
gap: 4px;
min-width: 0;
}
/* 桌面端隐藏标题行和底部行的特殊样式 */
.item-title-row {
display: flex;
align-items: flex-start;
}
.item-bottom-row {
display: none;
}
.btn-delete {
display: none;
}
.quantity-wrap {
display: none;
}
.item-price-val {
display: none;
}
.image-placeholder {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: rgba(255, 255, 255, 0.2);
}
.item-info {
display: flex;
flex-direction: column;
gap: 6px;
min-width: 0;
}
.item-name {
font-size: 14px;
font-weight: 500;
color: #fff;
margin: 0;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.item-tags {
display: flex;
gap: 6px;
flex-wrap: wrap;
}
.item-off {
color: #f56c6c;
font-size: 12px;
margin: 0;
}
.item-price {
text-align: center;
width: 100px;
flex-shrink: 0;
.price-current {
font-size: 15px;
font-weight: 600;
color: #fff;
}
}
.item-quantity {
width: 140px;
flex-shrink: 0;
.stock-tip {
font-size: 11px;
color: #f59e0b;
}
}
.item-subtotal {
text-align: center;
width: 100px;
flex-shrink: 0;
.subtotal-price {
font-size: 16px;
font-weight: 600;
color: #4e6ef2;
}
}
.item-action {
width: 60px;
display: flex;
justify-content: center;
flex-shrink: 0;
}
/* 移动端底部行 - 桌面端隐藏 */
.item-mobile-bottom {
display: none;
}
.checkout-section {
background: #2d2d44;
border: 1px solid rgba(255, 255, 255, 0.06);
border-radius: 12px;
padding: 20px;
}
.section-title {
display: flex;
align-items: center;
gap: 8px;
font-size: 16px;
font-weight: 600;
color: #fff;
margin: 0 0 16px 0;
.el-icon {
color: #4e6ef2;
}
}
.address-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.address-item {
display: flex;
gap: 12px;
padding: 16px;
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.06);
border-radius: 8px;
cursor: pointer;
transition: all 0.2s;
&:hover {
border-color: rgba(78, 110, 242, 0.3);
background: rgba(78, 110, 242, 0.05);
}
&.active {
border-color: #4e6ef2;
background: rgba(78, 110, 242, 0.1);
}
}
.address-radio {
display: flex;
align-items: flex-start;
padding-top: 2px;
.checked {
color: #4e6ef2;
font-size: 20px;
}
.unchecked {
width: 18px;
height: 18px;
border-radius: 50%;
border: 2px solid rgba(255, 255, 255, 0.2);
}
}
.address-content {
flex: 1;
min-width: 0;
}
.address-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 6px;
flex-wrap: wrap;
}
.address-name {
font-size: 15px;
font-weight: 600;
color: #fff;
}
.address-phone {
font-size: 14px;
color: rgba(255, 255, 255, 0.6);
}
.address-detail {
font-size: 13px;
color: rgba(255, 255, 255, 0.5);
line-height: 1.5;
}
.no-address {
text-align: center;
padding: 30px 0;
p {
color: rgba(255, 255, 255, 0.5);
margin-bottom: 16px;
}
}
.add-address-btn {
margin-top: 12px;
}
.payment-methods {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
&.single {
grid-template-columns: 1fr;
max-width: 200px;
}
}
.payment-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
padding: 16px 12px;
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.06);
border-radius: 8px;
cursor: pointer;
transition: all 0.2s;
position: relative;
&:hover {
border-color: rgba(78, 110, 242, 0.3);
background: rgba(78, 110, 242, 0.05);
}
&.active {
border-color: #4e6ef2;
background: rgba(78, 110, 242, 0.1);
}
.payment-icon {
font-size: 28px;
color: #4e6ef2;
}
.payment-name {
font-size: 13px;
color: rgba(255, 255, 255, 0.8);
}
.check-icon {
position: absolute;
top: 8px;
right: 8px;
color: #4e6ef2;
font-size: 16px;
}
}
.cart-summary-panel {
background: #2d2d44;
border: 1px solid rgba(255, 255, 255, 0.06);
border-radius: 12px;
padding: 24px;
position: sticky;
top: 20px;
align-self: start;
}
.summary-title {
font-size: 18px;
font-weight: 600;
color: #fff;
margin: 0 0 20px 0;
padding-bottom: 16px;
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}
.summary-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
font-size: 14px;
color: rgba(255, 255, 255, 0.7);
span:last-child {
color: #fff;
font-weight: 500;
}
}
.free-shipping {
color: #10b981 !important;
}
.summary-divider {
height: 1px;
background: rgba(255, 255, 255, 0.06);
margin: 16px 0;
}
.summary-total {
margin-bottom: 16px;
span:first-child {
font-size: 16px;
font-weight: 500;
color: rgba(255, 255, 255, 0.85);
}
.total-amount {
font-size: 24px;
font-weight: 700;
color: #f56c6c;
}
}
.summary-count {
text-align: center;
font-size: 14px;
color: rgba(255, 255, 255, 0.5);
margin-bottom: 16px;
strong {
color: #4e6ef2;
font-size: 16px;
}
}
.checkout-btn {
width: 100%;
height: 48px;
font-size: 16px;
font-weight: 600;
background: linear-gradient(135deg, #4e6ef2, #7c5cfc);
border: none;
border-radius: 8px;
&:disabled {
opacity: 0.5;
}
}
.summary-actions {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid rgba(255, 255, 255, 0.06);
}
:deep(.el-checkbox__label) {
color: rgba(255, 255, 255, 0.6);
}
:deep(.el-checkbox__input.is-checked .el-checkbox__inner) {
background: #4e6ef2;
border-color: #4e6ef2;
}
:deep(.el-input-number) {
width: 110px;
}
:deep(.el-input-number .el-input__wrapper) {
background: rgba(255, 255, 255, 0.06);
border: 1px solid rgba(255, 255, 255, 0.08);
box-shadow: none;
}
:deep(.el-input-number .el-input__inner) {
color: #fff;
}
@media (max-width: 1000px) {
.cart-container {
grid-template-columns: 1fr;
}
.cart-summary-panel {
position: static;
margin-bottom: 20px;
}
}
@media (max-width: 768px) {
.cart-page {
padding: 8px !important;
padding-bottom: 16px !important;
}
.cart-container {
display: flex !important;
flex-direction: column !important;
gap: 12px !important;
width: 100% !important;
}
/* 订单摘要放底部 */
.cart-left {
order: 1 !important;
width: 100% !important;
}
.cart-summary-panel {
order: 2 !important;
position: static !important;
width: 100% !important;
box-sizing: border-box !important;
}
.cart-header {
display: none !important;
}
.cart-main {
width: 100% !important;
}
.cart-list {
max-height: none !important;
overflow: visible !important;
width: 100% !important;
display: flex !important;
flex-direction: column !important;
gap: 10px !important;
}
/* 商品项 */
.cart-item {
display: flex !important;
flex-direction: row !important;
padding: 12px !important;
gap: 10px !important;
align-items: flex-start !important;
width: 100% !important;
box-sizing: border-box !important;
background: #2d2d44 !important;
border-radius: 8px !important;
border-bottom: none !important;
}
/* 隐藏桌面端元素 */
.cart-item > .item-price,
.cart-item > .item-quantity,
.cart-item > .item-subtotal,
.cart-item > .item-action {
display: none !important;
}
/* 复选框 */
.cart-item > .item-check {
flex: 0 0 20px !important;
width: 20px !important;
margin-right: 0 !important;
margin-top: 2px !important;
}
/* 图片 */
.cart-item > .item-image-wrap {
flex: 0 0 80px !important;
width: 80px !important;
margin-right: 0 !important;
}
.item-image-wrap .item-image {
width: 80px !important;
height: 80px !important;
border-radius: 4px !important;
}
/* 内容区 */
.cart-item > .item-content {
flex: 1 1 0 !important;
min-width: 0 !important;
display: flex !important;
flex-direction: column !important;
gap: 6px !important;
}
/* 标题行:名称 + 删除 */
.item-content .item-title-row {
display: flex !important;
justify-content: space-between !important;
align-items: flex-start !important;
gap: 8px !important;
}
.item-content .item-name {
flex: 1 !important;
font-size: 14px !important;
line-height: 1.4 !important;
margin: 0 !important;
color: #fff !important;
word-break: break-all !important;
display: -webkit-box !important;
-webkit-line-clamp: 2 !important;
-webkit-box-orient: vertical !important;
overflow: hidden !important;
}
/* 删除按钮 */
.item-content .btn-delete {
display: flex !important;
align-items: center !important;
justify-content: center !important;
flex-shrink: 0 !important;
width: 28px !important;
height: 28px !important;
border-radius: 4px !important;
background: rgba(245, 108, 108, 0.1) !important;
cursor: pointer !important;
transition: all 0.2s !important;
}
.item-content .btn-delete:hover {
background: rgba(245, 108, 108, 0.2) !important;
}
.item-content .btn-delete .el-icon {
font-size: 14px !important;
color: #f56c6c !important;
}
/* 标签 */
.item-content .item-tags {
display: flex !important;
flex-wrap: wrap !important;
gap: 4px !important;
}
.item-content .item-tags .el-tag {
font-size: 10px !important;
padding: 0 4px !important;
height: 16px !important;
line-height: 14px !important;
}
/* 底部行:价格 + 数量 */
.item-content .item-bottom-row {
display: flex !important;
justify-content: space-between !important;
align-items: center !important;
margin-top: auto !important;
padding-top: 4px !important;
}
/* 价格 */
.item-content .item-price-val {
display: block !important;
font-size: 16px !important;
font-weight: 600 !important;
color: #f2270c !important;
}
/* 数量选择器 */
.item-content .quantity-wrap {
display: block !important;
}
.item-content .quantity-wrap :deep(.el-input-number) {
width: 110px !important;
}
.item-content .quantity-wrap :deep(.el-input-number .el-input__wrapper) {
padding: 0 38px !important;
}
.item-content .quantity-wrap :deep(.el-input-number__decrease) {
width: 32px !important;
left: 2px !important;
}
.item-content .quantity-wrap :deep(.el-input-number__increase) {
width: 32px !important;
right: 2px !important;
}
.item-content .quantity-wrap :deep(.el-input-number .el-input__inner) {
font-size: 14px !important;
text-align: center !important;
padding: 0 !important;
}
/* 地址 */
.checkout-section {
padding: 12px !important;
border-radius: 8px !important;
}
.section-title {
font-size: 14px !important;
margin-bottom: 10px !important;
}
.address-item {
padding: 10px !important;
gap: 10px !important;
}
.address-name {
font-size: 14px !important;
}
.address-phone {
font-size: 13px !important;
}
.address-detail {
font-size: 12px !important;
}
/* 支付方式 */
.payment-methods {
grid-template-columns: repeat(2, 1fr) !important;
gap: 8px !important;
}
.payment-item {
padding: 10px 6px !important;
}
.payment-icon {
font-size: 22px !important;
}
.payment-name {
font-size: 12px !important;
}
/* 订单摘要 */
.cart-summary-panel {
padding: 12px !important;
border-radius: 8px !important;
}
.summary-title {
font-size: 15px !important;
margin-bottom: 12px !important;
padding-bottom: 10px !important;
}
.summary-row {
font-size: 13px !important;
margin-bottom: 8px !important;
}
.summary-total .total-amount {
font-size: 18px !important;
}
.summary-count {
font-size: 13px !important;
margin-bottom: 12px !important;
}
.checkout-btn {
height: 44px !important;
font-size: 15px !important;
}
.summary-actions {
margin-top: 12px !important;
padding-top: 12px !important;
}
}
</style>