From d1327abeae52cb9e5139525779ba32678bc48665 Mon Sep 17 00:00:00 2001 From: dzl <786316265@qq.com> Date: Wed, 15 Oct 2025 17:25:43 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=BC=98=E6=83=A0=E5=88=B8?= =?UTF-8?q?=E7=9A=84=E9=80=89=E6=8B=A9=E4=B8=8E=E4=BD=BF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/BuyDetails.vue | 162 ++++++++++++++++++++++------------- src/views/CouponManage.vue | 1 - src/views/MyProfile.vue | 6 +- src/views/Pay.vue | 150 ++++++++++++++++++++++++++------ src/views/ProductSummary.vue | 70 ++++++++------- 5 files changed, 263 insertions(+), 126 deletions(-) diff --git a/src/views/BuyDetails.vue b/src/views/BuyDetails.vue index a2718c7..0e09614 100644 --- a/src/views/BuyDetails.vue +++ b/src/views/BuyDetails.vue @@ -64,10 +64,10 @@ class="spec-item" :class="{ active: selectedSpecs[specName]?.id === option.id, - disabled: availableSpecs[specName] && !availableSpecs[specName][option.id] + disabled: availableSpecs[specName] && !availableSpecs[specName][option.id] && selectedSpecs[specName]?.id !== option.id }" @click="selectSpec(specName, option)" - :disabled="availableSpecs[specName] && !availableSpecs[specName][option.id]" + :disabled="availableSpecs[specName] && !availableSpecs[specName][option.id] && selectedSpecs[specName]?.id !== option.id" > {{ option.name }} @@ -158,9 +158,53 @@ const totalPointsPrice = computed(() => { return product.value.points_price * quantity.value }) +// 动态计算当前场景下必须选择的规格名称集合(基于已选项与后端可用组合) +const requiredSpecNames = computed(() => { + const allSpecNames = Object.keys(specGroups.value) + if (!product.value || !Array.isArray(product.value.specifications)) { + return allSpecNames + } + + const availableSpecs = product.value.specifications.filter(s => s.is_available) + + // 已选的规格项ID + const selectedIds = allSpecNames + .map(name => selectedSpecs.value[name]?.id) + .filter(Boolean) + + // 能与当前已选项兼容的可用规格规则集合 + const candidateSpecs = availableSpecs.filter(spec => { + const detailIds = spec.spec_details.map(d => d.id) + return selectedIds.every(id => detailIds.includes(id)) + }) + + if (candidateSpecs.length === 0) { + // 若暂无兼容组合,默认要求全部(防止误判) + return allSpecNames + } + + // 计算每个候选组合涉及的规格名称集 + const nameSets = candidateSpecs.map(spec => { + const names = spec.spec_details.map(d => d.spec_name) + return Array.from(new Set(names)) + }) + + // 优先选择包含当前已选规格名称的最小集合 + const selectedNames = allSpecNames.filter(name => !!selectedSpecs.value[name]) + const filteredByIntent = nameSets + .filter(set => selectedNames.every(n => set.includes(n))) + .sort((a, b) => a.length - b.length) + + if (filteredByIntent.length > 0) return filteredByIntent[0] + + // 兜底:选择最小的集合 + nameSets.sort((a, b) => a.length - b.length) + return nameSets[0] +}) + const canPurchase = computed(() => { - const specNames = Object.keys(specGroups.value) - const allSpecsSelected = specNames.every(specName => selectedSpecs.value[specName]) + const required = requiredSpecNames.value + const allSpecsSelected = required.every(specName => selectedSpecs.value[specName]) return allSpecsSelected && quantity.value > 0 }) @@ -177,40 +221,38 @@ const decreaseQuantity = () => { } } -// 检查规格组合是否有效 +// 检查规格组合是否有效(忽略顺序,按ID集合匹配) const isValidCombination = (testSelection) => { const selectedIds = [] const specNames = Object.keys(specGroups.value) - - // 按规格名称顺序收集选中的规格ID,转换为顺序编号 + + // 收集已选规格的真实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占位 + selectedIds.push(testSelection[specName].id) } }) - - // 如果还没有选择完所有规格,检查部分选择是否与任何有效组合兼容 - 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] - }) + + if (!product.value || !Array.isArray(product.value.specifications)) { + return false + } + + // 仅考虑后端标记为可用的规格组合 + const availableSpecs = product.value.specifications.filter(s => s.is_available) + + // 部分选择:只要存在一个规格规则,其spec_details包含所有已选ID即可 + if (selectedIds.length < specNames.length) { + return availableSpecs.some(spec => { + const detailIds = spec.spec_details.map(d => d.id) + return selectedIds.every(id => detailIds.includes(id)) }) } - - // 如果选择了所有规格,检查完整组合是否有效 - const combinationKey = selectedIds.join('-') - return validCombinations.value.includes(combinationKey) + + // 完整选择:必须存在一个规格规则,其ID集合与所选集合相等(忽略顺序) + return availableSpecs.some(spec => { + const detailIds = spec.spec_details.map(d => d.id) + return detailIds.length === selectedIds.length && selectedIds.every(id => detailIds.includes(id)) + }) } // 更新可选规格状态 @@ -232,16 +274,20 @@ const updateAvailableSpecs = () => { // 选择规格 const selectSpec = (specName, option) => { - // 检查该选项是否被禁用 + // 二次点击已选中项则取消选择 + if (selectedSpecs.value[specName]?.id === option.id) { + delete selectedSpecs.value[specName] + updateAvailableSpecs() + return + } + + // 检查该选项是否被禁用(但允许点击已选项取消) 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() } @@ -307,16 +353,15 @@ const parseSpecifications = (specifications) => { // 转换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开始) + // 使用规格项自身ID作为映射,避免与后端combination_key不一致 finalSpecGroups[specName].forEach(option => { - specIdToOrderMap[option.id] = orderCounter++ + specIdToOrderMap[option.id] = option.id }) }) @@ -327,7 +372,7 @@ const parseSpecifications = (specifications) => { specIdToOrder.value = specIdToOrderMap console.log('有效的规格组合键:', validCombinationKeys) - console.log('规格ID到顺序编号映射:', specIdToOrderMap) + console.log('规格ID映射(按ID本身):', specIdToOrderMap) // 初始化可选规格状态 updateAvailableSpecs() @@ -336,36 +381,31 @@ const parseSpecifications = (specifications) => { console.log('解析后的规格分组:', finalSpecGroups) } -// 根据选中的规格组合找到对应的规格规则ID +// 根据选中的规格组合找到对应的规格规则ID(忽略顺序,按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) - } + const required = requiredSpecNames.value + const selectedIds = required + .map(name => selectedSpecs.value[name]?.id) + .filter(Boolean) + + // 只有在必选规格都已选择时才匹配具体规则 + if (selectedIds.length !== required.length) return null + + const availableSpecs = product.value.specifications?.filter(s => s.is_available) || [] + + const specification = availableSpecs.find(spec => { + const detailIds = spec.spec_details.map(d => d.id) + return detailIds.length === selectedIds.length && selectedIds.every(id => detailIds.includes(id)) }) - - // 生成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 () => { - // 检查是否选择了所有必需的规格 - const specNames = Object.keys(specGroups.value) - for (const specName of specNames) { + // 只校验当前场景下的“必选规格”是否已选择 + const required = requiredSpecNames.value + for (const specName of required) { if (!selectedSpecs.value[specName]) { ElMessage.warning(`请选择${specName}`) return @@ -375,7 +415,7 @@ const handlePurchase = async () => { // 获取选中规格对应的规格规则ID const specificationId = getSelectedSpecificationId() if (!specificationId) { - ElMessage.error('所选规格组合无效,请重新选择') + ElMessage.error('所选规格组合无效或不可用,请重新选择') return } diff --git a/src/views/CouponManage.vue b/src/views/CouponManage.vue index b482c42..b8cb990 100644 --- a/src/views/CouponManage.vue +++ b/src/views/CouponManage.vue @@ -27,7 +27,6 @@ const couponList = ref([]) const getCouponList = async () => { const response = await api.get(`/coupon/user/${user.id}`) couponList.value = response.data.coupon - console.log(123,couponList.value) } const getCouponName = (couponType) => { diff --git a/src/views/MyProfile.vue b/src/views/MyProfile.vue index 8d33d98..9001445 100644 --- a/src/views/MyProfile.vue +++ b/src/views/MyProfile.vue @@ -33,7 +33,6 @@ 普通用户 - @@ -922,6 +1012,10 @@ onMounted(() => { font-weight: 500; } +.coupon-selector { + margin-bottom: 8px; +} + .amount-section, .items-section, .payment-method-section { diff --git a/src/views/ProductSummary.vue b/src/views/ProductSummary.vue index c4339c6..cfc85ad 100644 --- a/src/views/ProductSummary.vue +++ b/src/views/ProductSummary.vue @@ -50,10 +50,13 @@ + + +
-