新增优惠券的选择与使用
This commit is contained in:
@@ -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"
|
||||
>
|
||||
<span class="spec-label">{{ option.name }}</span>
|
||||
</div>
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
</template>
|
||||
<el-tag v-else type="primary">普通用户</el-tag>
|
||||
</div>
|
||||
<!-- <button class="logout-btn" @click="handleLogout">退出登录</button>-->
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="auth-buttons">
|
||||
@@ -298,7 +297,8 @@ export default {
|
||||
const functionItems = ref([
|
||||
{image: "/imgs/mainpage/jiaoyijilu.png", text: "购物车", path: "/cart"},
|
||||
{image: "/imgs/mainpage/dingdanchaxun.png", text: "地址", path: "/address"},
|
||||
{image: "/imgs/mainpage/kefuzhongxin.png", text: "收藏", path: ""}
|
||||
{image: "/imgs/mainpage/kefuzhongxin.png", text: "收藏", path: ""},
|
||||
{image: "/imgs/mainpage/xitonggonggao.png", text: "卡包管理", path: "/couponmanage"},
|
||||
]);
|
||||
|
||||
// 加载账户信息
|
||||
@@ -828,7 +828,7 @@ export default {
|
||||
|
||||
.function-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
|
||||
@@ -174,6 +174,13 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 优惠券选择 -->
|
||||
<div class="coupon-selector">
|
||||
<el-select v-model="selectedCoupon" placeholder="请选择优惠券">
|
||||
<el-option v-for="coupon in couponList" :key="coupon.id" :label="getProgress(coupon.couponInfo.type, coupon.couponInfo.discount === '0.00' ? coupon.couponInfo.price === '0.00' ? coupon.couponInfo.precent : coupon.couponInfo.price : coupon.couponInfo.discount, coupon.couponInfo.for_a_amount)" :value="coupon.id" />
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<!-- 支付方式选择 -->
|
||||
<div class="payment-method-section">
|
||||
<h3 class="section-title">支付方式</h3>
|
||||
@@ -284,6 +291,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 底部支付按钮 -->
|
||||
@@ -374,7 +382,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import {
|
||||
@@ -386,10 +394,13 @@ import {
|
||||
} from '@element-plus/icons-vue'
|
||||
import api, { buyAPI } from '@/utils/api'
|
||||
import { getImageUrl } from '@/config'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const user = useUserStore().user
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(false)
|
||||
const paying = ref(false)
|
||||
@@ -403,6 +414,18 @@ const paymentData = ref({
|
||||
items: [] // 添加商品列表
|
||||
})
|
||||
|
||||
const couponList = ref([])
|
||||
const selectedCoupon = ref()
|
||||
|
||||
const getProgress = (couponType, couponInfo1, couponInfo2) => {
|
||||
const couponName = {
|
||||
discount_for_a_amount: '满减券:'+'满'+couponInfo1+'元减'+couponInfo2+'元',
|
||||
deduction: '抵扣券:'+'抵扣'+couponInfo1+'元',
|
||||
discount: '折扣券:'+'折扣'+couponInfo1+'%',
|
||||
}
|
||||
return couponName[couponType] || couponType
|
||||
}
|
||||
|
||||
// 计算商品总价
|
||||
const totalRongdouPrice = computed(() => {
|
||||
return paymentData.value.items.reduce((sum, item) => {
|
||||
@@ -418,10 +441,11 @@ const totalPointsPrice = computed(() => {
|
||||
}, 0)
|
||||
})
|
||||
|
||||
// 计算人民币价格(1融豆 = 1元)
|
||||
// 计算人民币价格(1融豆 = 1元),并应用所选优惠券
|
||||
const getRMBPrice = () => {
|
||||
const totalRongdou = totalRongdouPrice.value
|
||||
return (totalRongdou).toFixed(2)
|
||||
const baseRMB = totalRongdouPrice.value
|
||||
const discountedRMB = applyCouponToRMB(baseRMB)
|
||||
return discountedRMB.toFixed(2)
|
||||
}
|
||||
// 用户余额数据
|
||||
const userBalance = ref({
|
||||
@@ -435,15 +459,52 @@ const selectedAddress = ref(null)
|
||||
|
||||
// 方法
|
||||
|
||||
// 统一兑换比例:1融豆 = 10000积分
|
||||
const EXCHANGE_RATE = 10000
|
||||
|
||||
// 根据当前选择的优惠券(通过 selectedCoupon 的 id),计算人民币层面的优惠后金额
|
||||
const applyCouponToRMB = (baseRMB) => {
|
||||
const couponObj = couponList.value.find(c => c.id === selectedCoupon.value)
|
||||
if (!couponObj || !couponObj.couponInfo) return Math.max(0, baseRMB)
|
||||
const info = couponObj.couponInfo
|
||||
|
||||
// 解析字段为数字
|
||||
const type = info.type
|
||||
const threshold = Number(info.for_a_amount ?? 0)
|
||||
const minus = Number(info.discount ?? info.price ?? 0)
|
||||
const percent = Number(info.precent ?? 0) // 如 80 表示 8 折
|
||||
|
||||
let result = baseRMB
|
||||
if (type === 'discount_for_a_amount') {
|
||||
// 满减券:满足门槛则直接减固定金额
|
||||
if (baseRMB >= threshold) {
|
||||
result = baseRMB - minus
|
||||
}
|
||||
} else if (type === 'deduction') {
|
||||
// 抵扣券:直接减固定金额(无门槛或门槛已通过)
|
||||
result = baseRMB - minus
|
||||
} else if (type === 'discount') {
|
||||
// 折扣券:按折扣百分比计算,例如 percent=80 表示 8 折
|
||||
const factor = percent > 0 ? (percent / 100) : 1
|
||||
result = baseRMB * factor
|
||||
}
|
||||
|
||||
// 不允许出现负数
|
||||
return Math.max(0, Number(result))
|
||||
}
|
||||
|
||||
const selectPaymentMethod = async (method) => {
|
||||
// 仅基于商品的points_price价值总和计算支付总额
|
||||
// 同时基于融豆总价(人民币等价)与积分总价计算,均应用优惠券
|
||||
const totalPointsPrice = paymentData.value.items.reduce((sum, item) => sum + (item.points_price * item.quantity), 0)
|
||||
const EXCHANGE_RATE = 10000 // 1融豆 = 10000积分
|
||||
const totalRMBBase = totalRongdouPrice.value
|
||||
const discountedRMB = applyCouponToRMB(totalRMBBase)
|
||||
const discountedPoints = Math.ceil(discountedRMB * EXCHANGE_RATE)
|
||||
|
||||
// 支付宝支付和微信支付直接选择,不需要余额检查
|
||||
if (method === 'alipay_wap' || method === 'wechat_h5') {
|
||||
selectedPaymentMethod.value = method
|
||||
paymentData.value.totalAmount = totalPointsPrice
|
||||
// 第三方支付以人民币为准
|
||||
paymentData.value.totalAmount = discountedRMB
|
||||
paymentData.value.pointsAmount = 0
|
||||
paymentData.value.beansAmount = 0
|
||||
return
|
||||
@@ -453,10 +514,10 @@ const selectPaymentMethod = async (method) => {
|
||||
if (!isPaymentMethodAvailable(method)) {
|
||||
let message = ''
|
||||
if (method === 'beans') {
|
||||
const requiredBeans = Math.ceil(totalPointsPrice / EXCHANGE_RATE)
|
||||
const requiredBeans = Math.ceil(discountedPoints / EXCHANGE_RATE)
|
||||
message = `融豆余额不足,当前余额:${userBalance.value.beans},需要:${requiredBeans}`
|
||||
} else if (method === 'points') {
|
||||
message = `积分余额不足,当前余额:${userBalance.value.points},需要:${totalPointsPrice}`
|
||||
message = `积分余额不足,当前余额:${userBalance.value.points},需要:${discountedPoints}`
|
||||
} else if (method === 'mixed') {
|
||||
message = `余额不足,无法完成支付`
|
||||
}
|
||||
@@ -468,25 +529,25 @@ const selectPaymentMethod = async (method) => {
|
||||
|
||||
// 根据选择的支付方式更新显示的金额
|
||||
if (method === 'beans') {
|
||||
// 融豆支付:按积分/10000计算所需融豆
|
||||
const requiredBeans = Math.ceil(totalPointsPrice / EXCHANGE_RATE)
|
||||
paymentData.value.totalAmount = totalPointsPrice
|
||||
// 融豆支付:按人民币金额计算所需融豆(1豆=1元)
|
||||
const requiredBeans = Math.ceil(discountedRMB)
|
||||
paymentData.value.totalAmount = discountedRMB
|
||||
paymentData.value.pointsAmount = 0
|
||||
paymentData.value.beansAmount = requiredBeans
|
||||
} else if (method === 'points') {
|
||||
// 积分支付:直接使用积分
|
||||
paymentData.value.totalAmount = totalPointsPrice
|
||||
paymentData.value.pointsAmount = totalPointsPrice
|
||||
// 积分支付:按折后积分
|
||||
paymentData.value.totalAmount = discountedPoints
|
||||
paymentData.value.pointsAmount = discountedPoints
|
||||
paymentData.value.beansAmount = 0
|
||||
} else if (method === 'mixed') {
|
||||
// 积分+融豆支付:积分必须是10000的倍数,按(总积分-当前积分)/10000计算所需融豆
|
||||
let availablePoints = Math.min(userBalance.value.points, totalPointsPrice)
|
||||
// 积分+融豆支付:积分必须是10000的倍数,先用折后积分,再用融豆补足
|
||||
let availablePoints = Math.min(userBalance.value.points, discountedPoints)
|
||||
// 确保积分是10000的倍数
|
||||
availablePoints = Math.floor(availablePoints / 10000) * 10000
|
||||
const remainingPoints = totalPointsPrice - availablePoints
|
||||
const remainingPoints = discountedPoints - availablePoints
|
||||
const requiredBeans = Math.ceil(remainingPoints / EXCHANGE_RATE)
|
||||
|
||||
paymentData.value.totalAmount = totalPointsPrice
|
||||
paymentData.value.totalAmount = discountedPoints
|
||||
paymentData.value.pointsAmount = availablePoints
|
||||
paymentData.value.beansAmount = requiredBeans
|
||||
}// else if (method === 'alipay_wap') {
|
||||
@@ -564,9 +625,10 @@ const fetchUserBalance = async () => {
|
||||
|
||||
// 检查支付方式是否可用
|
||||
const isPaymentMethodAvailable = (method) => {
|
||||
// 仅基于商品的points_price价值总和计算
|
||||
const totalPointsPrice = paymentData.value.items.reduce((sum, item) => sum + (item.points_price * item.quantity), 0)
|
||||
const EXCHANGE_RATE = 10000 // 1融豆 = 10000积分
|
||||
// 基于折后金额计算可用性
|
||||
const totalRMBBase = totalRongdouPrice.value
|
||||
const discountedRMB = applyCouponToRMB(totalRMBBase)
|
||||
const totalPointsPrice = Math.ceil(discountedRMB * EXCHANGE_RATE)
|
||||
|
||||
switch (method) {
|
||||
case 'beans':
|
||||
@@ -590,9 +652,10 @@ const isPaymentMethodAvailable = (method) => {
|
||||
|
||||
// 检查支付方式是否应该显示
|
||||
const shouldShowPaymentMethod = (method) => {
|
||||
// 仅基于商品的points_price价值总和计算
|
||||
const totalPointsPrice = paymentData.value.items.reduce((sum, item) => sum + (item.points_price * item.quantity), 0)
|
||||
const EXCHANGE_RATE = 10000 // 1融豆 = 10000积分
|
||||
// 基于折后金额判断显示
|
||||
const totalRMBBase = totalRongdouPrice.value
|
||||
const discountedRMB = applyCouponToRMB(totalRMBBase)
|
||||
const totalPointsPrice = Math.ceil(discountedRMB * EXCHANGE_RATE)
|
||||
|
||||
switch (method) {
|
||||
case 'beans':
|
||||
@@ -662,6 +725,8 @@ const fetchPaymentData = async () => {
|
||||
orderNo: data.order_no || '', // 订单号
|
||||
items: items
|
||||
}
|
||||
|
||||
getCouponList()
|
||||
|
||||
// 根据用户余额自动选择支付方式
|
||||
if (shouldShowPaymentMethod('beans')) {
|
||||
@@ -748,10 +813,12 @@ const confirmPayment = async () => {
|
||||
paying.value = true
|
||||
|
||||
if (selectedPaymentMethod.value === 'wechat_h5' || selectedPaymentMethod.value === 'alipay_wap') {
|
||||
const amount = Number(getRMBPrice())
|
||||
// 以融豆总价(人民币等价)应用优惠券后的金额作为支付金额
|
||||
const discountedRMB = applyCouponToRMB(totalRongdouPrice.value)
|
||||
const response = await buyAPI.buy({
|
||||
paymentMethod: selectedPaymentMethod.value,
|
||||
amount: amount*100
|
||||
amount: Math.round(discountedRMB * 100),
|
||||
couponRecordId: selectedCoupon.value || undefined
|
||||
})
|
||||
|
||||
if (response?.data?.success) {
|
||||
@@ -773,7 +840,8 @@ const confirmPayment = async () => {
|
||||
addressId: selectedAddress.value.id,
|
||||
paymentMethod: selectedPaymentMethod.value,
|
||||
pointsAmount: paymentData.value.pointsAmount,
|
||||
beansAmount: paymentData.value.beansAmount
|
||||
beansAmount: paymentData.value.beansAmount,
|
||||
couponRecordId: selectedCoupon.value || undefined
|
||||
}
|
||||
|
||||
const response = await api.post('/orders/confirm-payment', orderData)
|
||||
@@ -795,11 +863,33 @@ const confirmPayment = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const getCouponList = async () => {
|
||||
try {
|
||||
const response = await api.get(`/coupon/user/${user.id}`)
|
||||
const allCoupons = response.data.coupon
|
||||
|
||||
const cartProductIds = paymentData.value.items.map(item => item.productId)
|
||||
|
||||
couponList.value = allCoupons.filter(coupon => coupon.use_time === null).filter(coupon => {
|
||||
return coupon.couponInfo.products_id.length === 0 ? true : coupon.couponInfo.products_id.some(id => cartProductIds.includes(id))
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('获取优惠券失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 页面初始化
|
||||
onMounted(() => {
|
||||
fetchPaymentData()
|
||||
})
|
||||
|
||||
// 优惠券变化时,按照当前选中的支付方式重新计算展示金额
|
||||
watch(selectedCoupon, () => {
|
||||
if (selectedPaymentMethod.value) {
|
||||
selectPaymentMethod(selectedPaymentMethod.value)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
@@ -922,6 +1012,10 @@ onMounted(() => {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.coupon-selector {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.amount-section,
|
||||
.items-section,
|
||||
.payment-method-section {
|
||||
|
||||
@@ -50,10 +50,13 @@
|
||||
<button class="button" @click="$router.push(`/product/${firstProduct.id}`)">立即购买</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div v-for="product in products.filter(p => p.id !== firstProduct.id)" :key="product.id" class="product-card">
|
||||
<!-- 轮播图部分 -->
|
||||
<div class="product-image">
|
||||
<div v-if="!detailsLoaded" class="carousel-skeleton">
|
||||
<div v-if="!product.images || product.images.length === 0" class="carousel-skeleton">
|
||||
<div class="skeleton-image"></div>
|
||||
<div class="skeleton-indicators">
|
||||
<div class="skeleton-dot"></div>
|
||||
@@ -65,9 +68,9 @@
|
||||
v-else
|
||||
:interval="4000"
|
||||
indicator-position="outside"
|
||||
style="min-height: 300px;"
|
||||
height="300px"
|
||||
>
|
||||
<el-carousel-item v-for="(image, index) in (productDetailsCache[product.id]?.images || [])" :key="index">
|
||||
<el-carousel-item v-for="(image, index) in product.images" :key="index">
|
||||
<img :src="getImageUrl(image)" :alt="product.name" class="carousel-image" />
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
@@ -109,7 +112,6 @@ import { getImageUrl } from '@/config'
|
||||
|
||||
const products = ref([])
|
||||
const firstProduct = ref([])
|
||||
const productDetail = ref([])
|
||||
const productDetailsCache = ref({}) // 缓存所有商品详情
|
||||
const loading = ref(true) // 添加加载状态
|
||||
const detailsLoaded = ref(false) // 商品详情是否加载完成
|
||||
@@ -150,7 +152,7 @@ const getProducts = async () => {
|
||||
console.log('解析后的商品数据:', products.value)
|
||||
|
||||
// 预加载所有商品详情
|
||||
await loadAllProductDetails()
|
||||
// await loadAllProductDetails()
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取商品失败:', error)
|
||||
@@ -210,38 +212,40 @@ const getFirstProduct = async () => {
|
||||
// }
|
||||
|
||||
// 预加载所有商品详情的函数
|
||||
const loadAllProductDetails = async () => {
|
||||
const uniqueProductIds = [...new Set(products.value.map(p => p.id))]
|
||||
// const loadAllProductDetails = async () => {
|
||||
// const uniqueProductIds = [...new Set(products.value.map(p => p.id))]
|
||||
|
||||
// 并行加载所有商品详情,避免逐个加载时的多次渲染
|
||||
const loadPromises = uniqueProductIds.map(async (id) => {
|
||||
try {
|
||||
const response = await api.get(`/products/${id}`)
|
||||
if (response.data && response.data.data && response.data.data.product) {
|
||||
return { id, product: response.data.data.product }
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`Failed to load details for product ${id}:`, error)
|
||||
return null
|
||||
}
|
||||
})
|
||||
// // 并行加载所有商品详情,避免逐个加载时的多次渲染
|
||||
// const loadPromises = uniqueProductIds.map(async (id) => {
|
||||
// try {
|
||||
// const response = await api.get(`/products/${id}`)
|
||||
// if (response.data && response.data.data && response.data.data.product) {
|
||||
// return { id, product: response.data.data.product }
|
||||
// }
|
||||
// } catch (error) {
|
||||
// console.warn(`Failed to load details for product ${id}:`, error)
|
||||
// return null
|
||||
// }
|
||||
// })
|
||||
|
||||
// 等待所有请求完成
|
||||
const results = await Promise.all(loadPromises)
|
||||
// // 等待所有请求完成
|
||||
// const results = await Promise.all(loadPromises)
|
||||
|
||||
// 一次性更新缓存,避免多次触发响应式更新
|
||||
const newCache = { ...productDetailsCache.value }
|
||||
results.forEach(result => {
|
||||
if (result) {
|
||||
// 不需要预处理图片路径,在模板中使用getImageUrl处理
|
||||
newCache[result.id] = result.product
|
||||
}
|
||||
})
|
||||
// // 一次性更新缓存,避免多次触发响应式更新
|
||||
// const newCache = { ...productDetailsCache.value }
|
||||
// results.forEach(result => {
|
||||
// if (result) {
|
||||
// // 不需要预处理图片路径,在模板中使用getImageUrl处理
|
||||
// newCache[result.id] = result.product
|
||||
// }
|
||||
// })
|
||||
|
||||
// console.log('123:', newCache,results)
|
||||
|
||||
// 一次性更新缓存和加载状态
|
||||
productDetailsCache.value = newCache
|
||||
detailsLoaded.value = true
|
||||
}
|
||||
// // 一次性更新缓存和加载状态
|
||||
// productDetailsCache.value = newCache
|
||||
// detailsLoaded.value = true
|
||||
// }
|
||||
|
||||
// 添加生命周期钩子来调用函数
|
||||
onMounted(async () => {
|
||||
|
||||
Reference in New Issue
Block a user