Files
inkreach-uni/pages/products/products.vue
T
2026-08-21 19:02:35 +08:00

464 lines
13 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>
<!-- 产品列表页 - 印美达 Inkreach -->
<view class="products-page">
<!-- 搜索栏 -->
<SearchBar
:keyword="searchKeyword"
@input="onSearchInput"
@filter="toggleFilter"
/>
<!-- 国家选择条 -->
<CountryBar
:list="countries"
:selected-id="selectedCountry"
@select="selectCountry"
/>
<!-- 已选筛选标签 -->
<ActiveFilters
:list="activeFilters"
@remove="removeFilter"
@clear-all="clearAllFilters"
/>
<!-- 主体内容侧边栏 + 产品列表 -->
<view class="content-body">
<!-- 左侧分类侧边栏 -->
<CategorySidebar
:list="categories"
:active-id="activeCategory"
@select="selectCategory"
/>
<!-- 右侧产品列表 -->
<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>
<!-- 筛选弹窗 -->
<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>