bug:订单详情问题修复

This commit is contained in:
jzp 2025-11-12 22:03:36 +08:00
parent bb958ce2fe
commit c4f85aa9dc
3 changed files with 179 additions and 83 deletions

View File

@ -1,46 +1,72 @@
export type PrDetailType = { export type ProductDetailType = {
adminCategoryId1: 146 adminCategoryId1: number
adminCategoryId2: 147 adminCategoryId2: number
appCategoryId1: 48 appCategoryId1: number
appCategoryId2: 56 appCategoryId2: number
createTime: 1762760434000 createTime: number
defaultSort: 0 defaultSort: number
frontPage: 1 frontPage: number
id: 73 id: number
isDelete: 0 isDelete: number
isFlash: 0 isFlash: number
isNew: 1 isNew: number
isTest: 0 isTest: number
mainImageUrl: 'https://heyuimage.ihzhy.com/prd/202511/9a77567562f87caf.jpg?key=xxxxxxx' mainImageUrl: string
modifyTime: 1762760434000 modifyTime: number
realSaleCount: 0 realSaleCount: number
showPromotionPrice: 0 showPromotionPrice: number
showSaleCount: 0 showSaleCount: number
showSalePrice: 0 showSalePrice: number
status: 'online' status: string
title: 'Akoya双珠海水戒指' title: string
vvSkuList: SkuType[] vvSkuList: SkuType[]
vvProductDetailList: [] vvProductDetailList: ProductDetailImagesType[]
vvProductPropertyList: [] vvProductPropertyList: ProudictProductPropertyType[]
} }
export type SkuType = { export type SkuType = {
createTime: number createTime: number
id: 364 id: number
isDelete: 0 isDelete: number
modifyTime: 1762760434000 modifyTime: number
productId: 73 productId: number
promotionPrice: 0 promotionPrice: number
showSaleCount: 0 showSaleCount: number
stock: 0 stock: number
imageUrl: string
salePrice: number
vvSkuPropertyValueList: SkuPropretyType[]
}
export type SkuPropretyType = {
createTime: number
id: number
isDelete: number
modifyTime: number
productId: number
productPropertyName: string
productPropertyValue: string
skuId: number
} }
export type ProductDetailImagesType = { export type ProductDetailImagesType = {
createTime: 1762760434000 createTime: number
detail: 'https://heyuimage.ihzhy.com/prd/202511/304182481ebfc9ab.jpg?key=xxxxxxx' detail: string
id: 187 id: number
isDelete: 0 isDelete: number
modifyTime: 1762760434000 modifyTime: number
productId: 73 productId: number
type: 1 type: number
}
export type ProudictProductPropertyType = {
createTime: number
defaultSort: number
id: number
isDelete: number
modifyTime: number
productId: number
productPropertyName: string
vvProductPropertyList?: ProudictProductPropertyType[] // 如需树状结构可用此字段
vvProductPropertyValueList: ProudictProductPropertyType[] // 按你原写法保持不变
} }

View File

@ -15,8 +15,8 @@ const service: AxiosInstance = axios.create({
timeout: 150000, // 请求超时时间 timeout: 150000, // 请求超时时间
}) })
const noEncryptWhiteList = ['/common/protocol/content', '/portal/common/protocol/content', '/bl-log/web/log/add'] // const noEncryptWhiteList = ['/common/protocol/content', '/portal/common/protocol/content', '/bl-log/web/log/add']
const noConsoleWhiteList = ['bl-log/web/log/add'] // const noConsoleWhiteList = ['bl-log/web/log/add']
// request拦截器 // request拦截器
service.interceptors.request.use( service.interceptors.request.use(

View File

@ -6,7 +6,7 @@
<div class="cart-wrap"> <div class="cart-wrap">
<p class="flex justify-between" @click="showAddressList = true"> <p class="flex justify-between" @click="showAddressList = true">
<van-icon name="location-o" class="mr-1" /><span class="max-w-12 mr-1">{{ curAddressData?.buyerName }}</span> <van-icon name="location-o" class="mr-1" /><span class="max-w-12 mr-1">{{ curAddressData?.buyerName }}</span>
<b class="font- flex-1">{{ (curAddressData?.province ?? '') + (curAddressData?.city ?? '') + (curAddressData?.district ?? '') + (curAddressData?.detail?? '') }}</b <b class="font- flex-1">{{ (curAddressData?.province ?? '') + (curAddressData?.city ?? '') + (curAddressData?.district ?? '') + (curAddressData?.detail ?? '') }}</b
><van-icon name="arrow" /> ><van-icon name="arrow" />
</p> </p>
</div> </div>
@ -19,6 +19,13 @@
</p> </p>
<p> <p>
<van-stepper v-model="formData.num" input-width=".4rem" button-size=".2rem" min="1" :max="curPageData.num" /> <van-stepper v-model="formData.num" input-width=".4rem" button-size=".2rem" min="1" :max="curPageData.num" />
<span class="text-[10px] text-hex-cccccc ml-[5px]">
(最大可购买{{ curPageData?.num }})
</span>
<span class="text-[10px] text-hex-cccccc ml-[3px]">
{{ curPageData?.num > 0 ? "有货" : "缺货"}}
</span>
</p> </p>
</div> </div>
</div> </div>
@ -28,11 +35,11 @@
<div class="cagetory"> <div class="cagetory">
<p <p
v-for="v in arr" v-for="v in arr"
:key="v.propertyId" :key="v.sid"
:class="{ 'theme-border-color theme-color': v.propertyId === curPropertyIds.find((it) => it.productPropertyName === key).id }" :class="[{ 'theme-border-color theme-color': v.label === curSelection[key] }, { disabled: v.disabled }]"
@click="onChooseProperty(v, key)" @click="!v.disabled && onChooseProperty(v, key)"
> >
{{ v?.label }} {{ v.label }} {{ v.num }}
</p> </p>
</div> </div>
</template> </template>
@ -49,59 +56,109 @@
<script lang="ts" setup> <script lang="ts" setup>
import api from '@/api' import api from '@/api'
import { requestPayment } from '@/utils/wx-minprogram' import { requestPayment } from '@/utils/wx-minprogram'
import type { SkuType } from '@/api/types/shop'
const model = defineModel<boolean>() const model = defineModel<boolean>()
const showAddressList = defineModel<boolean>('showAddressList') const showAddressList = defineModel<boolean>('showAddressList')
const props = defineProps<{ list: Array<any>; curAddressData: Recordable<any>; scene: 'order' | 'cart' }>() const props = defineProps<{ list: Array<any>; curAddressData: Recordable<any>; scene: 'order' | 'cart' }>()
const skuData = ref<any>({}) // skukeyvalue{ : [{ ''}]} type SkuOption = { sid: string; label: string; num: number; disabled?: boolean }
const commodityData = ref<any>({}) // valueskuId(keyid) const skuData = ref<Record<string, SkuOption[]>>({}) // skukey
const curPropertyIds = ref<any>([]) // id[{ id: 1, name: ''},{id: 2, name: ''}] name const commodityData = ref<Record<string, { id: number; imageUrl?: string; salePrice?: number; price?: number; num: number }>>({}) // sku
const curPageData = ref<any>({ price: '', imageUrl: '', num: 0 }) // const categoryOrder = ref<string[]>([]) //
const formData = ref<any>({ skuId: NaN, num: 1 }) const curSelection = ref<Record<string, string>>({})
const curPageData = ref({ price: 0, imageUrl: '', num: 0 })
const formData = ref({ skuId: NaN, num: 1 })
// pathset
const buildSignature = (order: string[], selection: Record<string, string>) => order.map((name) => `${name}:${selection[name] ?? ''}`).join('|')
// id
const buildOptionSid = (vv: SkuType['vvSkuPropertyValueList'][number]) => `${vv.productPropertyName}:${vv.productPropertyValue}`
watch(model, (val) => { watch(model, (val) => {
if (val) { if (!val) return
const { result, commodityMap, firstShopIds } = handleGetSkuList(props.list) const { result, commodityMap, firstSelection } = handleGetSkuList(props.list)
skuData.value = result skuData.value = result
commodityData.value = commodityMap commodityData.value = commodityMap
curPropertyIds.value = firstShopIds curSelection.value = firstSelection
const { pageData, skuId } = handleGetPageData(commodityData.value, curPropertyIds.value) syncCurrentSku()
curPageData.value = pageData updateOptionDisabled()
formData.value = { skuId, num: 1 }
}
}) })
const handleGetSkuList = (list: any[]) => { //
const result = {} as any // sku const updateOptionDisabled = () => {
const commodityMap = {} // Object.entries(skuData.value).forEach(([categoryName, options]) => {
let firstShopIds = [] options.forEach((opt) => {
list.forEach((item, i) => { const trialSelection = { ...curSelection.value, [categoryName]: opt.label }
const commodityValue = { id: item.id, imageUrl: item.imageUrl, salePrice: item.salePrice, price: item.promotionPrice, num: item.stock } const match = commodityData.value[buildSignature(categoryOrder.value, trialSelection)]
let commodityPropertyId = '' opt.disabled = !match || (match.num ?? 0) <= 0
if (i === 0) { })
firstShopIds = item.vvSkuPropertyValueList.map((child) => ({ id: child.id, productPropertyName: child.productPropertyName })) })
} }
item.vvSkuPropertyValueList.forEach((item2, index) => {
commodityPropertyId += item2.id + (index < item.vvSkuPropertyValueList.length - 1 ? '_' : '') const handleGetSkuList = (list: SkuType[]) => {
if (!result[item2.productPropertyName]) result[item2.productPropertyName] = [] const result: Record<string, SkuOption[]> = {}
if (result[item2.productPropertyName].some((child) => child.propertyId === item2.id)) return const commodityMap: Record<string, { id: number; imageUrl?: string; salePrice?: number; price?: number; num: number }> = {}
result[item2.productPropertyName].push({ propertyId: item2.id, label: item2.productPropertyValue }) //
const order = list[0]?.vvSkuPropertyValueList?.map((vv) => vv.productPropertyName) || []
categoryOrder.value = order
let firstSelection: Record<string, string> = {}
list.forEach((sku, index) => {
//
if (index === 0) {
firstSelection = Object.fromEntries(sku.vvSkuPropertyValueList.map((vv) => [vv.productPropertyName, vv.productPropertyValue]))
}
//
const selection = Object.fromEntries(sku.vvSkuPropertyValueList.map((vv) => [vv.productPropertyName, vv.productPropertyValue]))
//
const comboKey = buildSignature(order, selection)
commodityMap[comboKey] = {
id: sku.id,
imageUrl: sku.imageUrl,
salePrice: sku.salePrice,
price: sku.promotionPrice,
num: sku.stock,
}
//
sku.vvSkuPropertyValueList.forEach((vv, idx) => {
if (!result[vv.productPropertyName]) result[vv.productPropertyName] = []
const bucket = result[vv.productPropertyName]
const sid = buildOptionSid(vv)
const existing = bucket.find((item) => item.sid === sid)
const isLeafProperty = idx === order.length - 1
if (existing) {
// SKU SKU
existing.num = isLeafProperty ? sku.stock : existing.num + sku.stock
} else {
bucket.push({
sid,
label: vv.productPropertyValue,
num: sku.stock,
disabled: isLeafProperty ? sku.stock <= 0 : false,
})
}
}) })
commodityMap[commodityPropertyId] = commodityValue
}) })
return { result, commodityMap, firstShopIds } return { result, commodityMap, firstSelection }
} }
const onChooseProperty = (v, categoryName) => { const onChooseProperty = (v, categoryName) => {
curPropertyIds.value.find((it) => it.productPropertyName === categoryName).id = v.propertyId if (v.disabled) return
const { pageData, skuId } = handleGetPageData(commodityData.value, curPropertyIds.value)
curPageData.value = pageData curSelection.value[categoryName] = v.label
formData.value = { skuId, num: 1 } syncCurrentSku()
updateOptionDisabled()
} }
const handleGetPageData = (commodityData, curPropertyIds) => {
const data = commodityData[curPropertyIds.map((item) => item.id).join('_')] const syncCurrentSku = () => {
return { pageData: { price: data?.price, imageUrl: data?.imageUrl, num: data?.num }, skuId: data?.id } const data = commodityData.value[buildSignature(categoryOrder.value, curSelection.value)]
curPageData.value = { price: data?.price ?? 0, imageUrl: data?.imageUrl ?? '', num: data?.num ?? 0 }
formData.value = { skuId: data?.id ?? NaN, num: 1 }
} }
const onSubmit = async () => { const onSubmit = async () => {
@ -125,15 +182,18 @@ const onSubmit = async () => {
border-radius: 0.04rem; border-radius: 0.04rem;
overflow: hidden; overflow: hidden;
} }
.cart-cagetory-wrap { .cart-cagetory-wrap {
h4 { h4 {
font-weight: bold; font-weight: bold;
margin-top: 0.16rem; margin-top: 0.16rem;
margin-bottom: 0.12rem; margin-bottom: 0.12rem;
} }
.cagetory { .cagetory {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
& > p { & > p {
border: 1px solid #f2f2f2; border: 1px solid #f2f2f2;
box-sizing: border-box; box-sizing: border-box;
@ -146,14 +206,24 @@ const onSubmit = async () => {
border-radius: 0.04rem; border-radius: 0.04rem;
margin-right: 0.08rem; margin-right: 0.08rem;
margin-bottom: 0.08rem; margin-bottom: 0.08rem;
&.theme-border-color { &.theme-border-color {
background: #fff; background: #fff;
} }
&.disabled {
background: #eee;
color: #bbb;
border-color: #eee;
pointer-events: none;
}
} }
} }
} }
&.van-popup--bottom.van-popup--round { &.van-popup--bottom.van-popup--round {
border-radius: 0.08rem 0.08rem 0 0; border-radius: 0.08rem 0.08rem 0 0;
:deep(.van-popup__close-icon) { :deep(.van-popup__close-icon) {
top: 0.06rem; top: 0.06rem;
font-size: 0.2rem; font-size: 0.2rem;