467 lines
13 KiB
Vue
467 lines
13 KiB
Vue
<template>
|
||
<!-- 产品列表页 - 印美达 Inkreach -->
|
||
<view class="products-page">
|
||
<!-- 搜索栏 -->
|
||
<SearchBar
|
||
:keyword="searchKeyword"
|
||
@input="onSearchInput"
|
||
@filter="toggleFilter"
|
||
/>
|
||
|
||
<!-- 国家选择条 -->
|
||
<CountryBar
|
||
:list="countries"
|
||
:selected-id="selectedCountry"
|
||
@select="selectCountry"
|
||
/>
|
||
|
||
<!-- 主体内容:左侧分类侧边栏(固定宽度) + 右侧内容区(自动剩余宽度) -->
|
||
<view class="content-body">
|
||
<!-- 左侧分类侧边栏 -->
|
||
<CategorySidebar
|
||
:list="categories"
|
||
:active-id="activeCategory"
|
||
@select="selectCategory"
|
||
/>
|
||
|
||
<!-- 右侧内容区:上方筛选条件 + 下方商品滚动区 -->
|
||
<view class="right-wrap">
|
||
<!-- 已选筛选标签(只在右侧顶部出现,不挤压左边) -->
|
||
<ActiveFilters
|
||
:list="activeFilters"
|
||
@remove="removeFilter"
|
||
@clear-all="clearAllFilters"
|
||
/>
|
||
|
||
<!-- 产品列表(内含子分类条件+滚动商品) -->
|
||
<ProductGrid
|
||
:list="products"
|
||
:sub-categories="subCategories"
|
||
:active-sub-id="activeSubCategory"
|
||
:total="pagination.total"
|
||
:current="pagination.current"
|
||
:page-size="pagination.pageSize"
|
||
:total-pages="pagination.totalPages"
|
||
:page-numbers="pageNumbers"
|
||
:start="pagination.start"
|
||
:end="pagination.end"
|
||
:loading="loadingMore"
|
||
@sub-select="selectSubCategory"
|
||
@product-click="goDetail"
|
||
@page-change="loadGoods"
|
||
@load-more="loadMore"
|
||
/>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 筛选弹窗 -->
|
||
<FilterDrawer
|
||
:visible="showFilter"
|
||
:groups="filterGroups"
|
||
@close="closeFilter"
|
||
@toggle-tag="toggleFilterTag"
|
||
@reset="resetFilters"
|
||
@confirm="confirmFilters"
|
||
/>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import SearchBar from './components/SearchBar.vue'
|
||
import CountryBar from './components/CountryBar.vue'
|
||
import ActiveFilters from './components/ActiveFilters.vue'
|
||
import CategorySidebar from './components/CategorySidebar.vue'
|
||
import ProductGrid from './components/ProductGrid.vue'
|
||
import FilterDrawer from './components/FilterDrawer.vue'
|
||
|
||
import { getCountries, getCategories, getTagGroups, getGoods } from '@/api/public.js'
|
||
|
||
export default {
|
||
components: {
|
||
SearchBar,
|
||
CountryBar,
|
||
ActiveFilters,
|
||
CategorySidebar,
|
||
ProductGrid,
|
||
FilterDrawer
|
||
},
|
||
data() {
|
||
return {
|
||
searchKeyword: '',
|
||
selectedCountry: '',
|
||
countries: [],
|
||
activeCategory: 'all',
|
||
categories: [
|
||
{
|
||
id: 'all',
|
||
name: '全部商品'
|
||
}
|
||
],
|
||
subCategories: [],
|
||
activeSubCategory: '',
|
||
activeFilters: [],
|
||
showFilter: false,
|
||
filterGroups: [],
|
||
pagination: {
|
||
start: 1,
|
||
end: 10,
|
||
total: 0,
|
||
current: 1,
|
||
pageSize: 10,
|
||
totalPages: 0
|
||
},
|
||
pageNumbers: [],
|
||
loadingMore: false,
|
||
products: []
|
||
}
|
||
},
|
||
async onLoad() {
|
||
await this.initData()
|
||
this.loadSubCategories('all')
|
||
await this.loadGoods(1)
|
||
},
|
||
methods: {
|
||
async initData() {
|
||
try {
|
||
// 1. 国家列表
|
||
const countriesRes = await getCountries()
|
||
const countriesData = (countriesRes.data && countriesRes.data.data) || []
|
||
const countries = countriesData.map((c) => ({
|
||
id: c.id,
|
||
name: c.countryName,
|
||
flag: this.codeToEmoji(c.countryIcon)
|
||
}))
|
||
this.countries = countries
|
||
|
||
// 2. 默认不选中任何国家
|
||
await this.loadCategoryData('')
|
||
|
||
console.log('=== [Products API Test] ===')
|
||
console.log('[1] countries:', countries.length, '条')
|
||
console.log('=== [Test End] ===')
|
||
} catch (err) {
|
||
console.error('[Products initData Error]', err)
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 加载分类树和标签组(按国家过滤)
|
||
*/
|
||
async loadCategoryData(countryId) {
|
||
try {
|
||
const params = countryId ? { countryId: countryId } : {}
|
||
const [categoriesRes, tagGroupsRes] = await Promise.all([
|
||
getCategories(params),
|
||
getTagGroups(params)
|
||
])
|
||
|
||
// 分类树
|
||
const categoriesData = (categoriesRes.data && categoriesRes.data.data) || []
|
||
const categories = [{ id: 'all', name: '全部商品', children: [], productCount: 0 }]
|
||
categoriesData.forEach((cat) => {
|
||
categories.push({
|
||
id: cat.id,
|
||
name: cat.categoryName,
|
||
children: cat.children || [],
|
||
productCount: cat.productCount || 0
|
||
})
|
||
})
|
||
|
||
// 标签组
|
||
const tagGroupsData = (tagGroupsRes.data && tagGroupsRes.data.data) || []
|
||
const filterGroups = tagGroupsData.map((g) => ({
|
||
id: g.id,
|
||
title: g.groupName,
|
||
tags: (g.tags || []).map((t) => ({
|
||
id: t.id,
|
||
name: t.tagName,
|
||
color: t.tagColor,
|
||
fontColor: t.tagFontColor,
|
||
productCount: t.productCount || 0
|
||
})),
|
||
selected: []
|
||
}))
|
||
|
||
this.categories = categories
|
||
this.filterGroups = filterGroups
|
||
this.activeCategory = 'all'
|
||
this.subCategories = []
|
||
this.activeSubCategory = ''
|
||
|
||
console.log(
|
||
'[loadCategoryData] countryId=' + (countryId || 'all') +
|
||
', categories=' + categories.length +
|
||
', filterGroups=' + filterGroups.length
|
||
)
|
||
} catch (err) {
|
||
console.error('[loadCategoryData Error]', err)
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 加载商品列表(分页 + 筛选)
|
||
*/
|
||
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()
|
||
}
|
||
|
||
console.log('[loadGoods] params:', JSON.stringify(params))
|
||
|
||
const res = await getGoods(params)
|
||
const data = (res.data && res.data.data) || { items: [], total: 0, page: 1, pageSize: 10 }
|
||
const items = data.items || []
|
||
|
||
const products = items.map((item) => ({
|
||
id: item.goodId,
|
||
name: item.goodName,
|
||
price: item.price,
|
||
image: item.image,
|
||
tags: (item.tags || []).map(function (t) {
|
||
return {
|
||
text: t.tagName,
|
||
bg: t.tagColor,
|
||
color: t.tagFontColor
|
||
}
|
||
})
|
||
}))
|
||
|
||
const total = data.total || 0
|
||
const totalPages = Math.ceil(total / pageSize) || 1
|
||
var pageNumbers = []
|
||
for (var i = Math.max(1, currentPage - 2); i <= Math.min(totalPages, currentPage + 2); i++) {
|
||
pageNumbers.push(i)
|
||
}
|
||
|
||
this.products = products
|
||
this.pagination = {
|
||
start: total === 0 ? 0 : (currentPage - 1) * pageSize + 1,
|
||
end: Math.min(currentPage * pageSize, total),
|
||
total: total,
|
||
current: currentPage,
|
||
pageSize: pageSize,
|
||
totalPages: totalPages
|
||
}
|
||
this.pageNumbers = pageNumbers
|
||
this.loadingMore = false
|
||
|
||
console.log(
|
||
'[loadGoods] total=' + total +
|
||
', page=' + currentPage +
|
||
', items=' + products.length
|
||
)
|
||
} catch (err) {
|
||
console.error('[loadGoods Error]', err)
|
||
this.products = []
|
||
this.loadingMore = false
|
||
}
|
||
},
|
||
|
||
codeToEmoji(iconPath) {
|
||
if (!iconPath) return '🌐'
|
||
var match = iconPath.match(/\/([^/]+)\.\w+$/)
|
||
if (!match) return '🌐'
|
||
var code = match[1].toUpperCase()
|
||
if (code.length !== 2) return '🌐'
|
||
var A = 0x1f1e6
|
||
return String.fromCodePoint(A + code.charCodeAt(0) - 65) + String.fromCodePoint(A + code.charCodeAt(1) - 65)
|
||
},
|
||
|
||
loadSubCategories(catId) {
|
||
var subCats = []
|
||
if (catId !== 'all') {
|
||
var cat = this.categories.find(function (c) { return c.id === catId })
|
||
if (cat && cat.children && cat.children.length > 0) {
|
||
subCats = cat.children.map(function (child) {
|
||
return { id: child.id, name: child.categoryName }
|
||
})
|
||
}
|
||
}
|
||
this.subCategories = subCats
|
||
this.activeSubCategory = subCats.length > 0 ? subCats[0].id : ''
|
||
},
|
||
|
||
onSearchInput(val) {
|
||
this.searchKeyword = val
|
||
// 防抖搜索
|
||
if (this._searchTimer) clearTimeout(this._searchTimer)
|
||
var self = this
|
||
this._searchTimer = setTimeout(function () {
|
||
self.loadGoods(1)
|
||
}, 300)
|
||
},
|
||
|
||
async selectCountry(id) {
|
||
// 如果点击的是已选中的国家 → 取消选中
|
||
if (this.selectedCountry === id) {
|
||
this.selectedCountry = ''
|
||
this.activeFilters = this.activeFilters.filter((f) => f.key !== 'country')
|
||
await this.loadCategoryData('')
|
||
await this.loadGoods(1)
|
||
return
|
||
}
|
||
|
||
// 选中新国家
|
||
const country = this.countries.find((c) => c.id === id)
|
||
const filters = this.activeFilters.filter((f) => f.key !== 'country')
|
||
if (country) {
|
||
filters.push({
|
||
key: 'country',
|
||
label: country.name
|
||
})
|
||
}
|
||
this.selectedCountry = id
|
||
this.activeFilters = filters
|
||
await this.loadCategoryData(id)
|
||
await this.loadGoods(1)
|
||
},
|
||
|
||
async selectCategory(id) {
|
||
this.activeCategory = id
|
||
this.loadSubCategories(id)
|
||
await this.loadGoods(1)
|
||
},
|
||
|
||
selectSubCategory(id) {
|
||
this.activeSubCategory = id
|
||
},
|
||
|
||
toggleFilter() {
|
||
this.showFilter = !this.showFilter
|
||
},
|
||
|
||
closeFilter() {
|
||
this.showFilter = false
|
||
},
|
||
|
||
toggleFilterTag(e) {
|
||
const gIdx = e.groupIndex
|
||
const tagId = e.tagId
|
||
var groups = [...this.filterGroups]
|
||
var group = groups[gIdx]
|
||
if (!group) return
|
||
var selected = [...group.selected]
|
||
var idx = selected.indexOf(tagId)
|
||
if (idx > -1) {
|
||
selected.splice(idx, 1)
|
||
} else {
|
||
selected.push(tagId)
|
||
}
|
||
group.selected = selected
|
||
this.filterGroups = groups
|
||
},
|
||
|
||
async removeFilter(key) {
|
||
const filters = this.activeFilters.filter((f) => f.key !== key)
|
||
|
||
if (key === 'country') {
|
||
this.selectedCountry = ''
|
||
this.activeFilters = filters
|
||
await this.loadCategoryData('')
|
||
await this.loadGoods(1)
|
||
return
|
||
}
|
||
|
||
if (key.indexOf('tag_') === 0) {
|
||
var groupId = key.replace('tag_', '')
|
||
var groups = this.filterGroups.map(function (g) {
|
||
if (String(g.id) === String(groupId)) {
|
||
return Object.assign({}, g, { selected: [] })
|
||
}
|
||
return g
|
||
})
|
||
this.activeFilters = filters
|
||
this.filterGroups = groups
|
||
await this.loadGoods(1)
|
||
return
|
||
}
|
||
|
||
this.activeFilters = filters
|
||
},
|
||
|
||
async clearAllFilters() {
|
||
const hasCountry = this.activeFilters.some((f) => f.key === 'country')
|
||
this.activeFilters = []
|
||
if (hasCountry || this.selectedCountry) {
|
||
this.selectedCountry = ''
|
||
await this.loadCategoryData('')
|
||
}
|
||
var groups = this.filterGroups.map(function (g) {
|
||
return Object.assign({}, g, { selected: [] })
|
||
})
|
||
this.filterGroups = groups
|
||
await this.loadGoods(1)
|
||
},
|
||
|
||
resetFilters() {
|
||
var groups = this.filterGroups.map(function (g) {
|
||
return Object.assign({}, g, { selected: [] })
|
||
})
|
||
this.filterGroups = groups
|
||
},
|
||
|
||
async confirmFilters() {
|
||
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
|
||
.filter(function (t) { return group.selected.indexOf(t.id) > -1 })
|
||
.map(function (t) { return t.name })
|
||
if (labels.length > 0) {
|
||
filters.push({
|
||
key: 'tag_' + group.id,
|
||
label: labels.join(',')
|
||
})
|
||
}
|
||
}
|
||
})
|
||
this.activeFilters = filters
|
||
this.showFilter = false
|
||
await this.loadGoods(1)
|
||
},
|
||
|
||
goDetail(item) {
|
||
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)
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style>
|
||
@import './products.css';
|
||
</style>
|