From bf12be7cb13bc95f384f9272e9a814e0c3df66a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A7=E8=84=8F=E7=8B=BC?= <786316265@qq.com> Date: Thu, 28 Aug 2025 19:48:40 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AF=B9=E6=8E=A5=E4=BA=86=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/Address.vue | 2 +- src/views/BuyDetails.vue | 482 ++++++++++++++++++++++-------------- src/views/Cart.vue | 9 +- src/views/ProductDetail.vue | 14 -- 4 files changed, 300 insertions(+), 207 deletions(-) diff --git a/src/views/Address.vue b/src/views/Address.vue index ef4f39c..8dac98c 100644 --- a/src/views/Address.vue +++ b/src/views/Address.vue @@ -46,7 +46,7 @@
-
{{ address.province }} {{ address.city }} {{ address.district }}
+
{{ address.province_name }} {{ address.city_name }} {{ address.district_name }}
{{ address.detailAddress }}
diff --git a/src/views/BuyDetails.vue b/src/views/BuyDetails.vue index da1c7db..6500a0f 100644 --- a/src/views/BuyDetails.vue +++ b/src/views/BuyDetails.vue @@ -84,41 +84,26 @@
- -
-

颜色分类 ({{ categories.length }})

-
+ +
+

{{ specName }} ({{ specOptions.length }})

+
-
- -
-
-
{{ category.name }}
-
{{ category.description }}
-
-
-
-
- - -
-

尺寸

-
-
-
{{ size.label }}
-
{{ size.range }}
+ {{ option.name }}
@@ -188,15 +173,16 @@ const router = useRouter() const loading = ref(false) const product = ref(null) const quantity = ref(1) -const categories = ref([]) -const sizes = ref([]) -const selectedCategory = ref(null) -const selectedSize = ref(null) +const specGroups = ref({}) // 动态规格组 +const selectedSpecs = ref({}) // 选中的规格值 const addresses = ref([]) const selectedAddressId = ref('') const selectedAddress = ref(null) const orderNote = ref('') const showNoteEdit = ref(false) +const availableSpecs = ref({}) // 存储每个规格选项的可选状态 +const validCombinations = ref([]) // 存储有效的规格组合键 +const specIdToOrder = ref({}) // 规格ID到顺序编号的映射 // 计算属性 const totalPrice = computed(() => { @@ -205,7 +191,9 @@ const totalPrice = computed(() => { }) const canPurchase = computed(() => { - return selectedCategory.value && selectedSize.value && quantity.value > 0 + const specNames = Object.keys(specGroups.value) + const allSpecsSelected = specNames.every(specName => selectedSpecs.value[specName]) + return allSpecsSelected && quantity.value > 0 }) // 方法 @@ -221,12 +209,73 @@ const decreaseQuantity = () => { } } -const selectCategory = (category) => { - selectedCategory.value = category +// 检查规格组合是否有效 +const isValidCombination = (testSelection) => { + const selectedIds = [] + const specNames = Object.keys(specGroups.value) + + // 按规格名称顺序收集选中的规格ID,转换为顺序编号 + specNames.forEach(specName => { + if (testSelection[specName]) { + const specId = testSelection[specName].id + const orderNumber = specIdToOrder.value[specId] + selectedIds.push(orderNumber) + } else { + selectedIds.push(null) // 未选择的规格用null占位 + } + }) + + // 如果还没有选择完所有规格,检查部分选择是否与任何有效组合兼容 + if (selectedIds.includes(null)) { + return validCombinations.value.some(combinationKey => { + const keyParts = combinationKey.split('-').map(k => parseInt(k)) + + // 检查当前部分选择是否与这个combination_key兼容 + return selectedIds.every((selectedOrder, index) => { + // 如果该位置未选择,则兼容 + if (selectedOrder === null) return true + // 如果该位置已选择,检查是否匹配 + return selectedOrder === keyParts[index] + }) + }) + } + + // 如果选择了所有规格,检查完整组合是否有效 + const combinationKey = selectedIds.join('-') + return validCombinations.value.includes(combinationKey) } -const selectSize = (size) => { - selectedSize.value = size +// 更新可选规格状态 +const updateAvailableSpecs = () => { + const specNames = Object.keys(specGroups.value) + const newAvailableSpecs = {} + + specNames.forEach(specName => { + newAvailableSpecs[specName] = {} + specGroups.value[specName].forEach(option => { + // 检查如果选择这个选项,是否存在有效的组合 + const testSelection = { ...selectedSpecs.value, [specName]: option } + newAvailableSpecs[specName][option.id] = isValidCombination(testSelection) + }) + }) + + availableSpecs.value = newAvailableSpecs +} + +// 选择规格 +const selectSpec = (specName, option) => { + // 检查该选项是否被禁用 + if (availableSpecs.value[specName] && !availableSpecs.value[specName][option.id]) { + ElMessage.warning('该规格组合不可选,请选择其他规格') + return + } + + selectedSpecs.value[specName] = option + console.log(`选择${specName}:`, option) + console.log('当前选中的所有规格:', selectedSpecs.value) + + // 更新可选规格状态 + updateAvailableSpecs() } const getProductInfo = async () => { @@ -240,7 +289,11 @@ const getProductInfo = async () => { } const response = await api.get(`/products/${productId}`) - product.value = response.data.data.product + const productData = response.data.data.product + product.value = productData + + // 从商品规格中解析颜色分类和尺寸 + parseSpecifications(productData.specifications || []) } catch (error) { ElMessage.error('获取商品信息失败') router.go(-1) @@ -249,57 +302,106 @@ const getProductInfo = async () => { } } -const getCategories = async () => { - try { - const productId = route.query.productId - const response = await api.get(`/products/${productId}/categories`) - categories.value = response.data.data.categories || [] - } catch (error) { - console.error('获取分类信息失败:', error) - } -} - -const getSizes = async () => { - try { - const productId = route.query.productId - const response = await api.get(`/products/${productId}/sizes`) - sizes.value = response.data.data.sizes || [] - } catch (error) { - console.error('获取尺寸信息失败:', error) - } -} - -const addToCart = async () => { - if (!canPurchase.value) { - ElMessage.error('请选择完整的商品信息') - return - } +// 解析商品规格信息,从spec_details中提取规格 +const parseSpecifications = (specifications) => { + console.log('原始规格数据:', specifications) - try { - const cartItem = { - productId: product.value.id, - quantity: quantity.value, - categoryId: selectedCategory.value.id, - sizeId: selectedSize.value.id, - points: product.value.points, - name: product.value.name, - image: product.value.images?.[0] || product.value.image, - stock: product.value.stock + const tempSpecGroups = {} + const validCombinationKeys = [] // 存储有效的combination_key + const specIdToOrderMap = {} // 规格ID到顺序编号的映射 + + // 遍历每个规格组合,提取combination_key + specifications.forEach(spec => { + if (spec.combination_key) { + validCombinationKeys.push(spec.combination_key) } - await api.post('/cart/add', cartItem) - ElMessage.success('商品已加入购物车!') - router.go(-1) // 返回上一页 - } catch (error) { - ElMessage.error('加入购物车失败,请重试') - } + // 遍历每个规格组合中的spec_details + spec.spec_details.forEach(detail => { + const specName = detail.spec_name + const specValue = detail.value + + if (!tempSpecGroups[specName]) { + tempSpecGroups[specName] = new Set() + } + + // 使用Set避免重复值 + tempSpecGroups[specName].add(JSON.stringify({ + id: detail.id, + name: specValue, + label: specValue, + description: specValue, + spec_name_id: detail.spec_name_id, + sort_order: detail.sort_order, + })) + }) + }) + + // 转换Set为数组并解析JSON + const finalSpecGroups = {} + let orderCounter = 1 + + Object.keys(tempSpecGroups).forEach(specName => { + finalSpecGroups[specName] = Array.from(tempSpecGroups[specName]).map(item => JSON.parse(item)) + // 按sort_order排序 + finalSpecGroups[specName].sort((a, b) => a.sort_order - b.sort_order) + + // 为每个规格选项分配顺序编号(从1开始) + finalSpecGroups[specName].forEach(option => { + specIdToOrderMap[option.id] = orderCounter++ + }) + }) + + specGroups.value = finalSpecGroups + + // 存储有效的combination_key和ID映射,用于验证 + validCombinations.value = validCombinationKeys + specIdToOrder.value = specIdToOrderMap + + console.log('有效的规格组合键:', validCombinationKeys) + console.log('规格ID到顺序编号映射:', specIdToOrderMap) + + // 初始化可选规格状态 + updateAvailableSpecs() + + // 输出解析后的规格信息 + console.log('解析后的规格分组:', finalSpecGroups) +} + +// 根据选中的规格组合找到对应的规格规则ID +const getSelectedSpecificationId = () => { + const specNames = Object.keys(specGroups.value) + const selectedIds = [] + + // 按规格名称顺序收集选中的规格ID,转换为顺序编号 + specNames.forEach(specName => { + if (selectedSpecs.value[specName]) { + const specId = selectedSpecs.value[specName].id + const orderNumber = specIdToOrder.value[specId] + selectedIds.push(orderNumber) + } + }) + + // 生成combination_key + const combinationKey = selectedIds.join('-') + + // 在specifications数组中找到对应的规格规则 + const specification = product.value.specifications?.find(spec => + spec.combination_key === combinationKey + ) + + return specification ? specification.id : null } // 立即购买功能 const handlePurchase = async () => { - if (!canPurchase.value) { - ElMessage.error('请选择完整的商品信息') - return + // 检查是否选择了所有必需的规格 + const specNames = Object.keys(specGroups.value) + for (const specName of specNames) { + if (!selectedSpecs.value[specName]) { + ElMessage.warning(`请选择${specName}`) + return + } } if (!selectedAddress.value) { @@ -307,22 +409,28 @@ const handlePurchase = async () => { return } + // 获取选中规格对应的规格规则ID + const specificationId = getSelectedSpecificationId() + if (!specificationId) { + ElMessage.error('所选规格组合无效,请重新选择') + return + } + try { // 创建单独的购买订单 const orderData = { - productId: product.value.id, - quantity: quantity.value, - categoryId: selectedCategory.value.id, - sizeId: selectedSize.value.id, - points: product.value.points, - name: product.value.name, - image: product.value.image, - stock: product.value.stock, - addressId: selectedAddress.value.id, - orderNote: orderNote.value + productId: product.value.id, // 商品ID + quantity: quantity.value, // 购买数量 + specificationId: specificationId, // 规格规则ID + points: product.value.points, // 商品积分价格 + name: product.value.name, // 商品名称 + image: product.value.image, // 商品图片 + stock: product.value.stock, // 商品库存 + addressId: selectedAddress.value.id, // 收货地址ID + orderNote: orderNote.value // 订单备注 } - const response = await api.post('/cart/buy-now', orderData) + const response = await api.post('/cart/buy-now', orderData)//立即购买 if (response.data.success) { const cartId = response.data.data.cartId @@ -344,21 +452,31 @@ const handlePurchase = async () => { // 添加到购物车功能(新增) const handleAddToCart = async () => { - if (!canPurchase.value) { - ElMessage.error('请选择完整的商品信息') + // 检查是否选择了所有必需的规格 + const specNames = Object.keys(specGroups.value) + for (const specName of specNames) { + if (!selectedSpecs.value[specName]) { + ElMessage.warning(`请选择${specName}`) + return + } + } + + // 获取选中规格对应的规格规则ID + const specificationId = getSelectedSpecificationId() + if (!specificationId) { + ElMessage.error('所选规格组合无效,请重新选择') return } try { const cartItem = { - productId: product.value.id, - quantity: quantity.value, - categoryId: selectedCategory.value.id, - sizeId: selectedSize.value.id, - points: product.value.points, - name: product.value.name, - image: product.value.image, - stock: product.value.stock + productId: product.value.id, // 商品ID + quantity: quantity.value, // 购买数量 + specificationId: specificationId, // 规格规则ID + points: product.value.points, // 商品积分价格 + name: product.value.name, // 商品名称 + image: product.value.image, // 商品图片 + stock: product.value.stock // 商品库存 } const response = await api.post('/cart/add', cartItem) @@ -378,17 +496,36 @@ const handleAddToCart = async () => { // 获取用户地址列表 const getAddressList = async () => { try { - const response = await api.get('/address/list') - addresses.value = response.data.data.addresses || [] - // 如果有默认地址,自动选中 - const defaultAddress = addresses.value.find(addr => addr.isDefault) - if (defaultAddress) { - selectedAddressId.value = defaultAddress.id - selectedAddress.value = defaultAddress + const response = await api.get('/addresses') + console.log('获取地址列表响应:', response) + if (response.data.success) { + // 根据接口文档转换数据格式,与Address.vue保持一致 + const addressList = response.data.data || [] + addresses.value = addressList.map(addr => ({ + id: addr.id, + recipientName: addr.receiver_name, + recipientPhone: addr.receiver_phone, + province: addr.province_name, + city: addr.city_name, + district: addr.district_name, + detailAddress: addr.detailed_address, + isDefault: addr.is_default, + labelName: addr.label_name, + labelColor: addr.label_color + })) + + // 如果有默认地址,自动选中 + const defaultAddress = addresses.value.find(addr => addr.isDefault) + if (defaultAddress) { + selectedAddressId.value = defaultAddress.id + selectedAddress.value = defaultAddress + } + } else { + throw new Error(response.data.message || '获取地址列表失败') } } catch (error) { console.error('获取地址列表失败:', error) - ElMessage.error('获取地址列表失败') + ElMessage.error(error.message || '获取地址列表失败') } } @@ -410,9 +547,7 @@ onMounted(() => { quantity.value = parseInt(initialQuantity) } - getProductInfo() - getCategories() - getSizes() + getProductInfo() // 商品信息中已包含规格信息,无需单独获取颜色分类和尺寸 getAddressList() }) @@ -538,8 +673,7 @@ onMounted(() => { text-align: center; } -.category-section, -.size-section, +.spec-section, .note-section, .payment-section { background: white; @@ -553,84 +687,52 @@ onMounted(() => { margin: 0 0 12px 0; } -.category-grid { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 12px; -} - -.category-item { +.spec-grid { display: flex; - gap: 8px; - padding: 8px; - border: 1px solid #eee; - border-radius: 8px; + flex-wrap: wrap; + gap: 10px; + margin-top: 10px; +} + +.spec-item { + display: flex; + align-items: center; + justify-content: center; + padding: 10px 15px; + border: 1px solid #e0e0e0; + border-radius: 6px; cursor: pointer; - transition: all 0.2s; -} - -.category-item.active { - border-color: #ffae00; - background: #fff7e6; -} - -.category-image { - width: 40px; - height: 40px; - border-radius: 4px; - overflow: hidden; -} - -.category-image img { - width: 100%; - height: 100%; - object-fit: cover; -} - -.category-info { - flex: 1; -} - -.category-name { - font-size: 14px; - font-weight: 500; - margin-bottom: 2px; -} - -.category-desc { - font-size: 12px; - color: #666; -} - -.size-grid { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 8px; -} - -.size-item { - padding: 12px 8px; - border: 1px solid #eee; - border-radius: 8px; + transition: all 0.3s ease; + min-width: 60px; text-align: center; - cursor: pointer; - transition: all 0.2s; } -.size-item.active { - border-color: #ffae00; - background: #fff7e6; +.spec-item:hover { + border-color: #ff6b35; + background-color: #fff5f2; } -.size-label { +.spec-item.active { + border-color: #ff6b35; + background-color: #ff6b35; + color: white; +} + +.spec-item.disabled { + background-color: #f5f5f5; + border-color: #e0e0e0; + color: #ccc; + cursor: not-allowed; +} + +.spec-item.disabled:hover { + background-color: #f5f5f5; + border-color: #e0e0e0; +} + +.spec-label { font-size: 14px; font-weight: 500; - margin-bottom: 4px; -} - -.size-range { - font-size: 12px; - color: #666; } .note-content { diff --git a/src/views/Cart.vue b/src/views/Cart.vue index 712037c..a5f2388 100644 --- a/src/views/Cart.vue +++ b/src/views/Cart.vue @@ -75,7 +75,7 @@
- {{ item.points }} + {{ item.product.points_price }}
@@ -172,13 +172,18 @@ const loadCartData = async () => { loading.value = true try { const response = await api.get('/cart') + console.log(response.data); + if (response.data.success) { - cartItems.value = response.data.data.map(item => ({ + cartItems.value = response.data.data.items.map(item => ({ ...item, selected: false })) + console.log(cartItems) } } catch (error) { + console.log(error); + ElMessage.error('加载购物车失败') } finally { loading.value = false diff --git a/src/views/ProductDetail.vue b/src/views/ProductDetail.vue index 2e7b7a9..2d077c5 100644 --- a/src/views/ProductDetail.vue +++ b/src/views/ProductDetail.vue @@ -123,19 +123,6 @@
- -
-

- 商品描述 -

-
-

{{ product.description }}

-
-
- 详情 -
-
-

@@ -947,7 +934,6 @@ watch( margin: 0 4px; /* tag 左右留白,更美观 */ } -.product-description, .product-details { margin-bottom: 20px; }