Files
medical-mall/components/consumer/SearchBar.uvue
2026-05-14 15:28:09 +08:00

88 lines
3.8 KiB
Plaintext

<template>
<view class="search-bar" v-if="mode==='readonly'" :style="{ paddingRight: internalCapsuleRight + 'px' }">
<view class="search-box" @click="onClick">
<image class="icon" src="/static/icons/search.png" />
<text class="placeholder">{{ placeholder }}</text>
</view>
<view class="right-slot">
<slot name="right"></slot>
<view v-if="showActionButton" class="action-btn" @click.stop="onActionClick">搜索</view>
<image v-if="showCamera" class="camera-icon" src="/static/icons/camera.png" @click.stop="$emit('camera')" />
</view>
</view>
<view class="search-bar" v-else :style="{ paddingRight: internalCapsuleRight + 'px' }">
<view class="search-box">
<image class="icon" src="/static/icons/search.png" />
<input class="search-input" :placeholder="placeholder" v-model="internalValue" @input="onInput" @confirm="onConfirm" />
</view>
<view class="right-slot">
<slot name="right"></slot>
<view v-if="showActionButton" class="action-btn" @click.stop="onActionClick">搜索</view>
<image v-if="showCamera" class="camera-icon" src="/static/icons/camera.png" @click.stop="$emit('camera')" />
</view>
</view>
</template>
<script lang="uts">
import { getNavMetrics } from '@/utils/navUtils.uts'
export default {
props: {
placeholder: { type: String, default: '搜索商品、店铺…' },
mode: { type: String, default: 'readonly' }, // 'readonly' | 'input'
autoNavigate: { type: Boolean, default: true },
capsuleRight: { type: [Number, String], default: 0 },
showActionButton: { type: Boolean, default: true },
showCamera: { type: Boolean, default: false },
value: { type: String, default: '' }
},
created() {
// 计算默认的胶囊右侧预留
try {
const metrics = getNavMetrics()
// 如果传入 props capsuleRight 为 0 或空,则使用自动计算值
this.internalCapsuleRight = (this.capsuleRight && Number(this.capsuleRight) > 0) ? Number(this.capsuleRight) : (metrics.navRightReserve || 0)
} catch (e) {
this.internalCapsuleRight = (this.capsuleRight && Number(this.capsuleRight) > 0) ? Number(this.capsuleRight) : 0
}
},
data() { return { internalValue: this.value, internalCapsuleRight: 0 } },
watch: {
value(newVal) { this.internalValue = newVal }
},
methods: {
onClick() {
this.$emit('click')
if (this.mode === 'readonly' && this.autoNavigate) {
try { uni.navigateTo({ url: '/pages/mall/consumer/search' }) } catch (e) {}
}
},
onActionClick() {
// 优先触发 action 事件,包含当前输入或占位词
const payload = (this.internalValue && this.internalValue.length > 0) ? this.internalValue : this.placeholder
this.$emit('action', payload)
},
onInput(e) {
const val = e && e.detail ? e.detail.value : (e || '')
this.internalValue = val
this.$emit('update:value', val)
this.$emit('input', val)
},
onConfirm(e) {
const confirmed = e && e.detail ? e.detail.value : this.internalValue
this.$emit('confirm', confirmed)
}
}
}
</script>
<style scoped>
.search-bar { display:flex; align-items:center; padding:12rpx 16rpx; background:transparent }
.search-box { flex:1; display:flex; align-items:center; background:#ffffff; border-radius:999rpx; padding:10rpx 12rpx; box-shadow:0 2rpx 8rpx rgba(0,0,0,0.04); height:44rpx; border:1rpx solid rgba(0,0,0,0.05) }
.icon { width:36rpx; height:36rpx; margin-right:12rpx }
.placeholder { color:#b8b8b8; font-size:26rpx; padding-right:6rpx }
.search-input { flex:1; height:40rpx; font-size:26rpx; color:#222; padding:0 }
.right-slot { margin-left:12rpx; display:flex; align-items:center }
.action-btn { background:#ff5000; color:#fff; padding:10rpx 20rpx; border-radius:16rpx; font-size:26rpx; font-weight:500 }
.camera-icon { width:28rpx; height:28rpx; margin-left:8rpx }
</style>