'商品列表和详情页接上接口'

This commit is contained in:
2026-08-22 11:40:57 +08:00
parent 872cbeae32
commit 075c4ceee2
4 changed files with 234 additions and 72 deletions
+181 -30
View File
@@ -1,5 +1,5 @@
<template>
<!-- 产品详情页 - 严格按 Figma 1544:530 实现 -->
<!-- 产品详情页 - 接入 /public/goods/{goodId} 接口 -->
<view class="detail-page">
<!-- 产品图片轮播 + 指示点 -->
<DetailSwiper :images="productImages" />
@@ -8,42 +8,44 @@
<ProductInfo :info="product" />
<!-- 灰色分隔条 -->
<view class="section-divider"></view>
<view class="section-divider" v-if="colors.length"></view>
<!-- 颜色选择 -->
<ColorSelector
v-if="colors.length"
:list="colors"
:selected-id="selectedColorId"
@select="selectColor"
/>
<!-- 灰色分隔条 -->
<view class="section-divider"></view>
<view class="section-divider" v-if="sizes.length"></view>
<!-- 尺码选择 -->
<SizeSelector
v-if="sizes.length"
:list="sizes"
:selected="selectedSize"
@select="selectSize"
/>
<!-- 灰色分隔条 -->
<view class="section-divider"></view>
<view class="section-divider" v-if="productParams.length"></view>
<!-- 产品参数含可展开的补充说明 -->
<ParamsTable :list="productParams" />
<ParamsTable v-if="productParams.length" :list="productParams" />
<!-- 灰色分隔条 -->
<view class="section-divider"></view>
<view class="section-divider" v-if="sizeChart.rows && sizeChart.rows.length"></view>
<!-- 产品尺码表 -->
<SizeChart :data="sizeChart" />
<SizeChart v-if="sizeChart.rows && sizeChart.rows.length" :data="sizeChart" />
<!-- 灰色分隔条 -->
<view class="section-divider"></view>
<view class="section-divider" v-if="packaging"></view>
<!-- 包装规格表 -->
<PackagingSpec :data="packaging" />
<!-- 包装规格表接口暂未提供预留 -->
<PackagingSpec v-if="packaging" :data="packaging" />
</view>
</template>
@@ -56,15 +58,7 @@ import ParamsTable from './components/ParamsTable.vue'
import SizeChart from './components/SizeChart.vue'
import PackagingSpec from './components/PackagingSpec.vue'
import {
mockProduct,
mockImages,
mockColors,
mockSizes,
mockParams,
mockSizeChart,
mockPackaging
} from './mock.js'
import { getGoodsDetail } from '@/api/public.js'
export default {
components: {
@@ -79,25 +73,182 @@ export default {
data() {
return {
productId: null,
product: mockProduct,
productImages: mockImages,
colors: mockColors,
// 默认选中"灰色"id=3),与 Figma 设计稿一致
selectedColorId: 3,
sizes: mockSizes,
// 默认选中 L,与 Figma 设计稿一致
selectedSize: 'L',
productParams: mockParams,
sizeChart: mockSizeChart,
packaging: mockPackaging
loading: false,
product: {
name: '',
sku: '',
country: '',
price: { symbol: '¥', integer: '0', decimal: '' },
quickInfo: []
},
productImages: [],
colors: [],
selectedColorId: '',
sizes: [],
selectedSize: '',
productParams: [],
sizeChart: {},
packaging: null
}
},
onLoad(options) {
if (options && options.id) {
this.productId = options.id
this.loadDetail(options.id)
}
},
methods: {
async loadDetail(goodId) {
if (!goodId) return
this.loading = true
try {
const res = await getGoodsDetail(goodId)
const detail = (res && res.data && res.data.data) || {}
this.applyDetail(detail)
} catch (err) {
console.error('[product-detail loadDetail Error]', err)
} finally {
this.loading = false
}
},
/**
* 将接口详情数据映射为各组件所需结构
*/
applyDetail(detail) {
// 1. 产品信息:名称 / SKU / 国家 / 价格 / 4 列快捷信息
this.product = {
name: detail.goodName || '—',
sku: detail.productCode || '—',
country: (detail.country && detail.country.countryName) || '—',
price: this.parsePrice(detail.price),
quickInfo: [
{ label: '生产工艺', value: (detail.details && detail.details.productionProcess) || '—' },
{
label: '发货时效',
value: detail.productionCycleHours ? detail.productionCycleHours + '小时' : '—'
},
{ label: '印花', value: (detail.details && detail.details.designArea) || '—' },
{ label: '产品编码', value: detail.productCode || '—' }
]
}
// 2. 图片画廊:按 sortOrder 升序,首图置顶
const media = detail.media || {}
let images = (media.images || [])
.slice()
.sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0))
.map((img) => img.url)
.filter((url) => url)
if (media.primaryImageUrl && images.indexOf(media.primaryImageUrl) === -1) {
images.unshift(media.primaryImageUrl)
}
if (!images.length && detail.image) {
images.push(detail.image)
}
this.productImages = images
// 3. 颜色:仅取启用项,按 sortOrder 升序
const colors = ((detail.options && detail.options.colors) || [])
.filter((c) => c.enabled !== false)
.slice()
.sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0))
.map((c) => ({ id: c.id, name: c.name, value: c.hex }))
this.colors = colors
this.selectedColorId = colors.length ? colors[0].id : ''
// 4. 尺码:仅取启用项,按 sortOrder 升序
const sizes = ((detail.options && detail.options.sizes) || [])
.filter((s) => s.enabled !== false)
.slice()
.sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0))
.map((s) => s.name)
this.sizes = sizes
this.selectedSize = sizes.length ? sizes[0] : ''
// 5. 产品参数(含可展开的"补充说明")
this.productParams = this.buildParams(detail)
// 6. 尺码表
this.sizeChart = this.buildSizeChart(detail.sizeChart)
// 7. 包装规格:接口暂未提供,预留为空
this.packaging = null
},
/**
* 价格拆分:'35' → { symbol:'¥', integer:'35', decimal:'' }
* '35.50' → { symbol:'¥', integer:'35', decimal:'.50' }
*/
parsePrice(price) {
const raw = price != null ? String(price) : ''
let integer = raw
let decimal = ''
const dot = raw.indexOf('.')
if (dot >= 0) {
integer = raw.slice(0, dot) || '0'
decimal = raw.slice(dot)
}
return { symbol: '¥', integer: integer || '0', decimal: decimal }
},
/**
* 构造产品参数列表:克重 / 材质 / 补充说明(可展开)
*/
buildParams(detail) {
const d = detail.details || {}
const params = []
if (detail.minWeightG) {
params.push({ label: '克重', value: detail.minWeightG + 'g' })
}
if (d.materialDescription) {
params.push({ label: '材质', value: d.materialDescription })
}
const expandableText = this.buildExpandableText(d)
if (expandableText) {
params.push({ label: '补充说明', expandable: true, value: expandableText })
}
return params
},
/**
* 拼接"补充说明"多段文本:材质说明/产品性能/适用情景/洗涤说明/图片要求/特殊说明/温馨提示
*/
buildExpandableText(d) {
const lines = []
if (d.materialDescription) lines.push('【 材质说明 】\n' + d.materialDescription)
if (d.productPerformance) lines.push('【 产品性能 】\n' + d.productPerformance)
if (d.applicableScenarios) lines.push('【 适用情景 】\n' + d.applicableScenarios)
if (d.washingInstructions) lines.push('【 洗涤说明 】\n' + d.washingInstructions)
if (d.pictureRequest) lines.push('【 图片要求 】\n' + d.pictureRequest)
if (d.specialDescription) lines.push('【 特殊说明 】\n' + d.specialDescription)
if (d.reminder) lines.push('【 温馨提醒 】\n' + d.reminder)
return lines.join('\n')
},
/**
* 构造尺码表:接口 measurements.key (chest/bodyLength/shoulder/sleeveLength) → 表格列
*/
buildSizeChart(sizeChart) {
if (!sizeChart || !sizeChart.rows || !sizeChart.rows.length) return {}
const columns = ['尺码', '肩宽', '胸围', '衣长', '袖长']
const rows = sizeChart.rows.map((row) => {
const measurements = row.measurements || []
const get = (key) => {
const m = measurements.find((x) => x.key === key)
return m ? m.cm : ''
}
return {
size: row.sizeName || '',
shoulder: get('shoulder'),
chest: get('chest'),
length: get('bodyLength'),
sleeve: get('sleeveLength')
}
})
return { unit: 'cm', columns, rows }
},
selectColor(id) {
this.selectedColorId = id
},