新增优惠券的选择与使用

This commit is contained in:
dzl
2025-10-15 17:25:43 +08:00
parent 0dc9f602f1
commit d1327abeae
5 changed files with 263 additions and 126 deletions

View File

@@ -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))
if (!product.value || !Array.isArray(product.value.specifications)) {
return false
}
// 检查当前部分选择是否与这个combination_key兼容
return selectedIds.every((selectedOrder, index) => {
// 如果该位置未选择,则兼容
if (selectedOrder === null) return true
// 如果该位置已选择,检查是否匹配
return selectedOrder === keyParts[index]
})
// 仅考虑后端标记为可用的规格组合
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 = []
const required = requiredSpecNames.value
const selectedIds = required
.map(name => selectedSpecs.value[name]?.id)
.filter(Boolean)
// 按规格名称顺序收集选中的规格ID转换为顺序编号
specNames.forEach(specName => {
if (selectedSpecs.value[specName]) {
const specId = selectedSpecs.value[specName].id
const orderNumber = specIdToOrder.value[specId]
selectedIds.push(orderNumber)
}
// 只有在必选规格都已选择时才匹配具体规则
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
}

View File

@@ -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) => {

View File

@@ -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;
}

View File

@@ -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':
@@ -663,6 +726,8 @@ const fetchPaymentData = async () => {
items: items
}
getCouponList()
// 根据用户余额自动选择支付方式
if (shouldShowPaymentMethod('beans')) {
await selectPaymentMethod('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 {

View File

@@ -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
// }
// })
// 一次性更新缓存和加载状态
productDetailsCache.value = newCache
detailsLoaded.value = true
}
// console.log('123:', newCache,results)
// // 一次性更新缓存和加载状态
// productDetailsCache.value = newCache
// detailsLoaded.value = true
// }
// 添加生命周期钩子来调用函数
onMounted(async () => {