'颜色尺码价格联动'
This commit is contained in:
@@ -10,10 +10,10 @@
|
||||
<!-- 灰色分隔条 -->
|
||||
<view class="section-divider" v-if="colors.length"></view>
|
||||
|
||||
<!-- 颜色选择 -->
|
||||
<!-- 颜色选择:根据 variants 传 enabled 可用态 -->
|
||||
<ColorSelector
|
||||
v-if="colors.length"
|
||||
:list="colors"
|
||||
:list="colorListWithAvailability"
|
||||
:selected-id="selectedColorId"
|
||||
@select="selectColor"
|
||||
/>
|
||||
@@ -21,10 +21,10 @@
|
||||
<!-- 灰色分隔条 -->
|
||||
<view class="section-divider" v-if="sizes.length"></view>
|
||||
|
||||
<!-- 尺码选择 -->
|
||||
<!-- 尺码选择:根据 variants 传 enabled 可用态 -->
|
||||
<SizeSelector
|
||||
v-if="sizes.length"
|
||||
:list="sizes"
|
||||
:list="sizeListWithAvailability"
|
||||
:selected="selectedSize"
|
||||
@select="selectSize"
|
||||
/>
|
||||
@@ -88,6 +88,8 @@ export default {
|
||||
selectedSize: '',
|
||||
productParams: [],
|
||||
sizeChart: {},
|
||||
// 变体列表(来自接口 variants),用于校验尺码+颜色组合、获取价格、包装规格
|
||||
variants: [],
|
||||
packaging: null
|
||||
}
|
||||
},
|
||||
@@ -169,11 +171,17 @@ export default {
|
||||
// 5. 产品参数(含可展开的"补充说明")
|
||||
this.productParams = this.buildParams(detail)
|
||||
|
||||
// 6. 尺码表
|
||||
// 6. 变体:用于尺码+颜色校验、价格联动、包装规格来源
|
||||
this.variants = Array.isArray(detail.variants) ? detail.variants.slice() : []
|
||||
|
||||
// 7. 尺码表
|
||||
this.sizeChart = this.buildSizeChart(detail.sizeChart)
|
||||
|
||||
// 7. 包装规格:接口暂未提供,预留为空
|
||||
this.packaging = null
|
||||
// 8. 包装规格(从 variants 按尺码去重生成)
|
||||
this.packaging = this.buildPackaging(this.variants)
|
||||
|
||||
// 9. 初始价格(按默认选中的 尺码+颜色 匹配变体价格)
|
||||
this.syncPriceByVariant()
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -249,11 +257,139 @@ export default {
|
||||
return { unit: 'cm', columns, rows }
|
||||
},
|
||||
|
||||
/**
|
||||
* 构建包装规格表(按尺码聚合 → 每行一个尺码)
|
||||
* 数据来源:variants[].{ sizeId, sizeName, boxLengthCm, boxWidthCm, boxHeightCm, weightG }
|
||||
*/
|
||||
buildPackaging(variants) {
|
||||
if (!Array.isArray(variants) || !variants.length) return null
|
||||
const CM_TO_IN = 0.393701
|
||||
const G_TO_LB = 0.00220462
|
||||
const map = new Map()
|
||||
variants.forEach((v) => {
|
||||
if (!v || !v.sizeId) return
|
||||
if (map.has(v.sizeId)) return // 按尺码取第一条(所有颜色的包装尺寸/重量一般相同)
|
||||
const l = parseFloat(v.boxLengthCm) || 0
|
||||
const w = parseFloat(v.boxWidthCm) || 0
|
||||
const h = parseFloat(v.boxHeightCm) || 0
|
||||
const g = parseFloat(v.weightG) || 0
|
||||
const cm3 = l * w * h
|
||||
const in3 = cm3 * Math.pow(CM_TO_IN, 3)
|
||||
map.set(v.sizeId, {
|
||||
size: v.sizeName || '',
|
||||
sizeId: v.sizeId,
|
||||
dimension: {
|
||||
cm: `${l.toFixed(1)}*${w.toFixed(1)}*${h.toFixed(1)}cm`,
|
||||
in: `${(l * CM_TO_IN).toFixed(2)}*${(w * CM_TO_IN).toFixed(2)}*${(h * CM_TO_IN).toFixed(2)}in`
|
||||
},
|
||||
volume: {
|
||||
cm: `${cm3.toFixed(2)}cm³`,
|
||||
in: `${in3.toFixed(2)}in³`
|
||||
},
|
||||
weight: {
|
||||
cm: `${g.toFixed(2)}g`,
|
||||
in: `${(g * G_TO_LB).toFixed(2)}lb`
|
||||
}
|
||||
})
|
||||
})
|
||||
if (!map.size) return null
|
||||
// 按 sizes 顺序排序(保持 S→M→L→XL 顺序)
|
||||
const orderMap = {}
|
||||
this.sizes.forEach((s, i) => (orderMap[s] = i))
|
||||
const rows = Array.from(map.values()).sort((a, b) => {
|
||||
const ai = orderMap[a.size] == null ? 999 : orderMap[a.size]
|
||||
const bi = orderMap[b.size] == null ? 999 : orderMap[b.size]
|
||||
return ai - bi
|
||||
})
|
||||
return {
|
||||
unit: 'cm',
|
||||
columns: ['尺码', '尺寸', '体积', '重量'],
|
||||
rows
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 根据当前 selectedColorId + selectedSize 找到匹配的 variant
|
||||
* 命中后更新 ProductInfo 的价格;找不到则保持原商品价格
|
||||
*/
|
||||
findVariant() {
|
||||
const sizeName = this.selectedSize
|
||||
const colorId = this.selectedColorId
|
||||
if (!sizeName || !colorId || !this.variants.length) return null
|
||||
return (
|
||||
this.variants.find(
|
||||
(v) =>
|
||||
v &&
|
||||
String(v.colorId) === String(colorId) &&
|
||||
String(v.sizeName) === String(sizeName)
|
||||
) || null
|
||||
)
|
||||
},
|
||||
syncPriceByVariant() {
|
||||
const v = this.findVariant()
|
||||
if (v && v.price != null) {
|
||||
this.$set(this.product, 'price', this.parsePrice(v.price))
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 颜色列表 + 根据已选尺码标记该颜色是否可用(有对应启用的 variant)
|
||||
* 数据项: { id, name, value, enabled }
|
||||
*/
|
||||
selectColor(id) {
|
||||
this.selectedColorId = id
|
||||
this.syncPriceByVariant()
|
||||
},
|
||||
/**
|
||||
* 尺码列表 + 根据已选颜色标记该尺码是否可用
|
||||
* 数据项: 直接返回字符串(SizeSelector 兼容),不可用的对应按钮会 disabled 掉,因此用对象包一层
|
||||
*/
|
||||
selectSize(size) {
|
||||
this.selectedSize = size
|
||||
this.syncPriceByVariant()
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
/**
|
||||
* 颜色列表 + 可用态标记(给 ColorSelector 用,用于 disabled 样式)
|
||||
* - 如果没选尺码:所有已 enabled 的颜色都可选
|
||||
* - 如果已选尺码:仅保留存在 (colorId + sizeName) 组合且 variant.enabled 的颜色
|
||||
*/
|
||||
colorListWithAvailability() {
|
||||
const sizeName = this.selectedSize
|
||||
return this.colors.map((c) => {
|
||||
let enabled = c.enabled !== false
|
||||
if (sizeName && this.variants.length) {
|
||||
enabled = this.variants.some(
|
||||
(v) =>
|
||||
v &&
|
||||
v.enabled !== false &&
|
||||
String(v.colorId) === String(c.id) &&
|
||||
String(v.sizeName) === String(sizeName)
|
||||
)
|
||||
}
|
||||
return Object.assign({}, c, { enabled })
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 尺码列表 + 可用态标记
|
||||
* SizeSelector 原始 list 是字符串数组,这里扩展为 [{ name, enabled }]
|
||||
*/
|
||||
sizeListWithAvailability() {
|
||||
const colorId = this.selectedColorId
|
||||
return this.sizes.map((s) => {
|
||||
let enabled = true
|
||||
if (colorId && this.variants.length) {
|
||||
enabled = this.variants.some(
|
||||
(v) =>
|
||||
v &&
|
||||
v.enabled !== false &&
|
||||
String(v.colorId) === String(colorId) &&
|
||||
String(v.sizeName) === String(s)
|
||||
)
|
||||
}
|
||||
return { name: s, enabled }
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user