Files
inkreach-uni/pages/products/products.vue
T
2026-08-22 19:05:26 +08:00

476 lines
14 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"
@blur="onSearchBlur"
@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"
/>
</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'
import { buildUrl } from '@/api/request.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,
icon: buildUrl(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
// 统一参数对象:由 buildQueryParams 统一拼装
const params = this.buildQueryParams(currentPage)
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
}
},
/**
* 构造统一的查询参数对象
* - 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+$/)
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 = ''
},
onSearchInput(val) {
// 仅更新关键词状态,搜索在失焦时触发
this.searchKeyword = val
},
onSearchBlur() {
this.loadGoods(1)
},
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)
},
async selectSubCategory(id) {
this.activeSubCategory = id
await this.loadGoods(1)
},
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}`
})
}
}
}
</script>
<style>
@import './products.css';
</style>