diff --git a/pages/products/components/FilterDrawer.vue b/pages/products/components/FilterDrawer.vue
index 7bc78be..0560db0 100644
--- a/pages/products/components/FilterDrawer.vue
+++ b/pages/products/components/FilterDrawer.vue
@@ -31,9 +31,23 @@
价格区间
-
+
-
-
+
@@ -58,7 +72,83 @@ export default {
default: () => []
}
},
+ data() {
+ return {
+ minPrice: '',
+ maxPrice: '',
+ minError: false,
+ maxError: false
+ }
+ },
methods: {
+ /**
+ * 清洗价格输入:只保留数字和小数点(仅一个)
+ */
+ sanitizePrice(v) {
+ if (v === '' || v === null || v === undefined) return ''
+ var s = String(v).replace(/[^\d.]/g, '')
+ var i = s.indexOf('.')
+ if (i > -1) {
+ s = s.slice(0, i + 1) + s.slice(i + 1).replace(/\./g, '')
+ }
+ return s
+ },
+ /**
+ * 校验单个价格:非空时必须是 ≥0 的数字,否则提示并清空,返回 false
+ */
+ validateSingle(key) {
+ var raw = this.sanitizePrice(this[key])
+ this[key] = raw
+ if (raw === '') return true
+ var num = parseFloat(raw)
+ if (isNaN(num) || num < 0) {
+ uni.showToast({ title: '价格不能小于0', icon: 'none' })
+ this[key] = ''
+ return false
+ }
+ return true
+ },
+ flashError(key) {
+ this[key] = true
+ setTimeout(() => { this[key] = false }, 1500)
+ },
+ onMinBlur() {
+ if (!this.validateSingle('minPrice')) return
+ // 最低价 > 已填的最高价 → 最低价钳制到最高价(反向同样不允许越界)
+ var min = parseFloat(this.minPrice)
+ var max = parseFloat(this.maxPrice)
+ if (this.minPrice !== '' && this.maxPrice !== '' && min > max) {
+ this.minPrice = this.maxPrice
+ this.flashError('minError')
+ uni.showToast({ title: '最低价不能大于最高价,已自动调整', icon: 'none' })
+ }
+ },
+ onMaxBlur() {
+ if (!this.validateSingle('maxPrice')) return
+ // 最高价 < 已填的最低价 → 冒红并改成最低价
+ var min = parseFloat(this.minPrice)
+ var max = parseFloat(this.maxPrice)
+ if (this.maxPrice !== '' && this.minPrice !== '' && max < min) {
+ this.maxPrice = this.minPrice
+ this.flashError('maxError')
+ uni.showToast({ title: '最高价不能小于最低价,已自动调整', icon: 'none' })
+ }
+ },
+ /**
+ * 点击确定前的价格校验,校验不过则阻止请求
+ */
+ validatePrices() {
+ if (!this.validateSingle('minPrice')) return false
+ if (!this.validateSingle('maxPrice')) return false
+ var min = parseFloat(this.minPrice)
+ var max = parseFloat(this.maxPrice)
+ if (this.minPrice !== '' && this.maxPrice !== '' && max < min) {
+ this.flashError('maxError')
+ uni.showToast({ title: '最高价不能小于最低价', icon: 'none' })
+ return false
+ }
+ return true
+ },
isSelected(group, tagId) {
return (group.selected || []).indexOf(tagId) > -1
},
@@ -66,10 +156,18 @@ export default {
this.$emit('toggle-tag', { groupIndex: gIdx, tagId })
},
onReset() {
+ this.minPrice = ''
+ this.maxPrice = ''
+ this.minError = false
+ this.maxError = false
this.$emit('reset')
},
onConfirm() {
- this.$emit('confirm')
+ if (!this.validatePrices()) return
+ this.$emit('confirm', {
+ minPrice: this.minPrice,
+ maxPrice: this.maxPrice
+ })
},
onClose() {
this.$emit('close')
@@ -128,6 +226,10 @@ export default {
}
.filter-drawer-body {
flex: 1;
+ /* 框架默认 width:100% + padding 时 content-box 会超宽 64rpx,
+ 导致右侧"最高价"输入框被挤出屏幕,必须 border-box */
+ width: 100%;
+ box-sizing: border-box;
padding: 0 32rpx;
}
.filter-group {
@@ -162,18 +264,26 @@ export default {
.filter-price-range {
display: flex;
align-items: center;
- gap: 20rpx;
+ justify-content: space-between;
}
.price-input {
- flex: 1;
+ /* 固定宽度,不依赖 flex 收缩(uni-input 自定义元素收缩行为不可靠) */
+ width: 230rpx;
+ box-sizing: border-box;
height: 72rpx;
padding: 0 20rpx;
background: #f5f5f5;
border-radius: 12rpx;
+ border: 2rpx solid transparent;
font-size: 26rpx;
color: #333333;
text-align: center;
}
+/* 校验失败冒红 */
+.price-input.input-error {
+ border-color: #ff0000;
+ background: #fff0f0;
+}
.price-divider {
font-size: 28rpx;
color: #999999;
diff --git a/pages/products/components/ProductGrid.vue b/pages/products/components/ProductGrid.vue
index 81809b7..525aa2e 100644
--- a/pages/products/components/ProductGrid.vue
+++ b/pages/products/components/ProductGrid.vue
@@ -42,10 +42,19 @@
{{ item.name }}
-
+
¥{{ item.price }}
起
+
+
+ {{ item.countryName }}
+
@@ -75,6 +84,8 @@ export default {
hasMore: { type: Boolean, default: true },
loading: { type: Boolean, default: false },
scrollTopVal: { type: Number, default: 0 },
+ // 未选择国家时显示商品所属国家标签
+ showCountryTag: { type: Boolean, default: false },
// 商品滚动区固定高度(px),由父组件动态计算传入
scrollHeight: { type: Number, default: 400 }
},
@@ -216,6 +227,34 @@ export default {
line-height: 1.2;
margin-left: 4rpx;
}
+/* 国家标签 - 价格右侧,国旗图标 + 国家名 */
+.country-tag {
+ display: inline-flex;
+ align-items: center;
+ gap: 6rpx;
+ margin-left: 12rpx;
+ padding: 4rpx 12rpx;
+ background: #f7f7f7;
+ border-radius: 20rpx;
+ max-width: 50%;
+ box-sizing: border-box;
+ overflow: hidden;
+}
+.country-tag-icon {
+ width: 28rpx;
+ height: 20rpx;
+ border-radius: 4rpx;
+ flex-shrink: 0;
+}
+.country-tag-name {
+ font-size: 20rpx;
+ font-weight: 500;
+ color: #666666;
+ line-height: 1.2;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
/* 底部加载提示 */
.load-tip {
diff --git a/pages/products/products.vue b/pages/products/products.vue
index 8c82bde..07d8c2c 100644
--- a/pages/products/products.vue
+++ b/pages/products/products.vue
@@ -46,6 +46,7 @@
:loading="loadingMore"
:scroll-top-val="scrollTopVal"
:scroll-height="productScrollHeight"
+ :show-country-tag="!selectedCountry"
@sub-select="selectSubCategory"
@product-click="goDetail"
@scroll-to-lower="onScrollToLower"
@@ -114,6 +115,8 @@ export default {
activeFilters: [],
showFilter: false,
filterGroups: [],
+ minPrice: '',
+ maxPrice: '',
pagination: {
total: 0,
current: 1,
@@ -255,7 +258,9 @@ export default {
id: item.goodId,
name: truncateProductTitle(item.goodName),
price: item.price,
- image: item.image
+ image: item.image,
+ countryName: (item.country && item.country.countryName) || '',
+ countryIcon: (item.country && item.country.countryIcon) ? buildUrl(item.country.countryIcon) : ''
}))
const total = data.total || 0
@@ -316,6 +321,13 @@ export default {
if (kw) {
params.keyword = kw
}
+ // 价格区间:两个可单独传,也可同时传
+ if (this.minPrice !== '' && this.minPrice !== null && this.minPrice !== undefined) {
+ params.minPrice = this.minPrice
+ }
+ if (this.maxPrice !== '' && this.maxPrice !== null && this.maxPrice !== undefined) {
+ params.maxPrice = this.maxPrice
+ }
const tagsList = (this.filterGroups || [])
.filter((g) => g.selected && g.selected.length > 0)
.map((g) => ({
@@ -514,6 +526,8 @@ export default {
async clearAllFilters() {
const hasCountry = this.activeFilters.some((f) => f.key === 'country')
this.activeFilters = []
+ this.minPrice = ''
+ this.maxPrice = ''
if (hasCountry || this.selectedCountry) {
this.selectedCountry = ''
await this.loadCategoryData('')
@@ -532,7 +546,10 @@ export default {
this.filterGroups = groups
},
- async confirmFilters() {
+ async confirmFilters(payload) {
+ payload = payload || {}
+ this.minPrice = payload.minPrice || ''
+ this.maxPrice = payload.maxPrice || ''
var filters = []
var countryFilter = this.activeFilters.find(function (f) { return f.key === 'country' })
if (countryFilter) filters.push(countryFilter)