Files
inkreach-uni/pages/product-detail/product-detail.vue
T

265 lines
8.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<!-- 产品详情页 - 接入 /public/goods/{goodId} 接口 -->
<view class="detail-page">
<!-- 产品图片轮播 + 指示点 -->
<DetailSwiper :images="productImages" />
<!-- 产品信息名称/SKU/国家/价格/4列快捷信息 -->
<ProductInfo :info="product" />
<!-- 灰色分隔条 -->
<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" v-if="sizes.length"></view>
<!-- 尺码选择 -->
<SizeSelector
v-if="sizes.length"
:list="sizes"
:selected="selectedSize"
@select="selectSize"
/>
<!-- 灰色分隔条 -->
<view class="section-divider" v-if="productParams.length"></view>
<!-- 产品参数含可展开的补充说明 -->
<ParamsTable v-if="productParams.length" :list="productParams" />
<!-- 灰色分隔条 -->
<view class="section-divider" v-if="sizeChart.rows && sizeChart.rows.length"></view>
<!-- 产品尺码表 -->
<SizeChart v-if="sizeChart.rows && sizeChart.rows.length" :data="sizeChart" />
<!-- 灰色分隔条 -->
<view class="section-divider" v-if="packaging"></view>
<!-- 包装规格表接口暂未提供预留 -->
<PackagingSpec v-if="packaging" :data="packaging" />
</view>
</template>
<script>
import DetailSwiper from './components/DetailSwiper.vue'
import ProductInfo from './components/ProductInfo.vue'
import ColorSelector from './components/ColorSelector.vue'
import SizeSelector from './components/SizeSelector.vue'
import ParamsTable from './components/ParamsTable.vue'
import SizeChart from './components/SizeChart.vue'
import PackagingSpec from './components/PackagingSpec.vue'
import { getGoodsDetail } from '@/api/public.js'
export default {
components: {
DetailSwiper,
ProductInfo,
ColorSelector,
SizeSelector,
ParamsTable,
SizeChart,
PackagingSpec
},
data() {
return {
productId: null,
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
},
selectSize(size) {
this.selectedSize = size
}
}
}
</script>
<style>
@import './product-detail.css';
</style>