diff --git a/pages/product-detail/product-detail.vue b/pages/product-detail/product-detail.vue
index 8f6658c..f322931 100644
--- a/pages/product-detail/product-detail.vue
+++ b/pages/product-detail/product-detail.vue
@@ -1,5 +1,5 @@
-
+
@@ -8,42 +8,44 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
+
+
@@ -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
},
diff --git a/pages/products/components/ProductGrid.vue b/pages/products/components/ProductGrid.vue
index 6c93797..123e916 100644
--- a/pages/products/components/ProductGrid.vue
+++ b/pages/products/components/ProductGrid.vue
@@ -13,7 +13,7 @@
-
+
1) this.$emit('page-change', this.current - 1) },
onNext() { if (this.current < this.totalPages) this.$emit('page-change', this.current + 1) },
- onGoPage(page) { this.$emit('page-change', page) },
- onLoadMore() { this.$emit('load-more') }
+ onGoPage(page) { this.$emit('page-change', page) }
}
}
diff --git a/pages/products/components/SearchBar.vue b/pages/products/components/SearchBar.vue
index 4dc380b..da47106 100644
--- a/pages/products/components/SearchBar.vue
+++ b/pages/products/components/SearchBar.vue
@@ -9,6 +9,7 @@
placeholder-class="search-placeholder"
:value="keyword"
@input="onInput"
+ @blur="onBlur"
/>
@@ -31,6 +32,9 @@ export default {
onInput(e) {
this.$emit('input', e.detail.value)
},
+ onBlur() {
+ this.$emit('blur')
+ },
onFilter() {
this.$emit('filter')
}
diff --git a/pages/products/products.vue b/pages/products/products.vue
index 8035690..20d6bd7 100644
--- a/pages/products/products.vue
+++ b/pages/products/products.vue
@@ -5,6 +5,7 @@
@@ -49,7 +50,6 @@
@sub-select="selectSubCategory"
@product-click="goDetail"
@page-change="loadGoods"
- @load-more="loadMore"
/>
@@ -199,29 +199,16 @@ export default {
},
/**
- * 加载商品列表(分页 + 筛选)
+ * 统一加载商品列表(分页 + 全部筛选条件)
+ * 所有触发点(搜索失焦 / 分类点击 / 国家切换 / 标签点击 / 确认筛选 / 分页)都走这里
*/
async loadGoods(page) {
try {
const currentPage = page || this.pagination.current || 1
const pageSize = this.pagination.pageSize || 10
- var params = {
- page: currentPage,
- pageSize: pageSize
- }
-
- if (this.selectedCountry) {
- params.countryId = this.selectedCountry
- }
-
- if (this.activeCategory && this.activeCategory !== 'all') {
- params.categoryId = this.activeCategory
- }
-
- if (this.searchKeyword && this.searchKeyword.trim()) {
- params.keyword = this.searchKeyword.trim()
- }
+ // 统一参数对象:由 buildQueryParams 统一拼装
+ const params = this.buildQueryParams(currentPage)
console.log('[loadGoods] params:', JSON.stringify(params))
@@ -274,6 +261,40 @@ export default {
}
},
+ /**
+ * 构造统一的查询参数对象
+ * - countryId / categoryId / keyword / page / pageSize 直传后端
+ * - categoryId:子分类优先,其次顶级分类
+ * - tags:对象数组 [{ tagGroupId, tagIds }],同组 OR、跨组 AND(后端处理)
+ */
+ buildQueryParams(page) {
+ const params = {
+ page: page || 1,
+ pageSize: this.pagination.pageSize || 10
+ }
+ if (this.selectedCountry) {
+ params.countryId = this.selectedCountry
+ }
+ const catId = this.activeSubCategory || (this.activeCategory !== 'all' ? this.activeCategory : '')
+ if (catId) {
+ params.categoryId = catId
+ }
+ const kw = (this.searchKeyword || '').trim()
+ if (kw) {
+ params.keyword = kw
+ }
+ const tagsList = (this.filterGroups || [])
+ .filter((g) => g.selected && g.selected.length > 0)
+ .map((g) => ({
+ tagGroupId: String(g.id),
+ tagIds: (g.selected || []).map((id) => String(id))
+ }))
+ if (tagsList.length > 0) {
+ params.tags = JSON.stringify(tagsList)
+ }
+ return params
+ },
+
codeToEmoji(iconPath) {
if (!iconPath) return '🌐'
var match = iconPath.match(/\/([^/]+)\.\w+$/)
@@ -295,17 +316,16 @@ export default {
}
}
this.subCategories = subCats
- this.activeSubCategory = subCats.length > 0 ? subCats[0].id : ''
+ this.activeSubCategory = ''
},
onSearchInput(val) {
+ // 仅更新关键词状态,搜索在失焦时触发
this.searchKeyword = val
- // 防抖搜索
- if (this._searchTimer) clearTimeout(this._searchTimer)
- var self = this
- this._searchTimer = setTimeout(function () {
- self.loadGoods(1)
- }, 300)
+ },
+
+ onSearchBlur() {
+ this.loadGoods(1)
},
async selectCountry(id) {
@@ -339,8 +359,9 @@ export default {
await this.loadGoods(1)
},
- selectSubCategory(id) {
+ async selectSubCategory(id) {
this.activeSubCategory = id
+ await this.loadGoods(1)
},
toggleFilter() {
@@ -421,7 +442,6 @@ export default {
var filters = []
var countryFilter = this.activeFilters.find(function (f) { return f.key === 'country' })
if (countryFilter) filters.push(countryFilter)
-
this.filterGroups.forEach(function (group) {
if (group.selected.length > 0) {
var labels = group.tags
@@ -444,18 +464,6 @@ export default {
uni.navigateTo({
url: `/pages/product-detail/product-detail?id=${item.id}`
})
- },
-
- loadMore() {
- if (this.loadingMore) {
- return
- }
- const nextPage = this.pagination.current + 1
- if (nextPage > this.pagination.totalPages) {
- return
- }
- this.loadingMore = true
- this.loadGoods(nextPage)
}
}
}