国家标注,价格筛选

This commit is contained in:
2026-08-31 14:26:05 +08:00
parent 3bd58e05c5
commit 502d8def61
3 changed files with 174 additions and 8 deletions
+115 -5
View File
@@ -31,9 +31,23 @@
<view class="filter-group">
<text class="filter-group-title">价格区间</text>
<view class="filter-price-range">
<input class="price-input" placeholder="最低价" type="number" />
<input
class="price-input"
:class="{ 'input-error': minError }"
placeholder="最低价"
type="number"
v-model="minPrice"
@blur="onMinBlur"
/>
<text class="price-divider">-</text>
<input class="price-input" placeholder="最高价" type="number" />
<input
class="price-input"
:class="{ 'input-error': maxError }"
placeholder="最高价"
type="number"
v-model="maxPrice"
@blur="onMaxBlur"
/>
</view>
</view>
</scroll-view>
@@ -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;
+40 -1
View File
@@ -42,10 +42,19 @@
<!-- 商品信息 -->
<view class="product-info">
<text class="product-name">{{ item.name }}</text>
<!-- 价格 + "起" -->
<!-- 价格 + "起" + 国家标签(未选国家时显示商品所属国家) -->
<view class="price-row">
<text class="product-price">{{ item.price }}</text>
<text class="price-unit"></text>
<view class="country-tag" v-if="showCountryTag && item.countryName">
<image
class="country-tag-icon"
:src="item.countryIcon"
mode="aspectFill"
v-if="item.countryIcon"
></image>
<text class="country-tag-name">{{ item.countryName }}</text>
</view>
</view>
</view>
</view>
@@ -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 {