Files

238 lines
5.7 KiB
Vue
Raw Permalink Normal View History

2026-08-19 08:17:18 +08:00
<template>
<!-- 主题搜索条:细描边 + 线框图标,不用 uview 灰底大按钮 -->
<view
class="ts"
:class="['ts--' + variant, { 'is-on': focused, 'is-off': disabled }]"
:style="wrapStyle"
@tap="onWrapTap"
>
<view class="ts-ico">
<view class="ts-ring" :style="{ borderColor: iconColor }" />
<view class="ts-stem" :style="{ background: iconColor }" />
</view>
<input
class="ts-field"
:style="{ color: text }"
:value="keyword"
:disabled="disabled"
:placeholder="placeholder"
:placeholder-style="phStyle"
confirm-type="search"
@input="onInput"
@confirm="emitSearch"
@focus="focused = true"
@blur="focused = false"
/>
<view v-if="keyword && !disabled" class="ts-x" @tap.stop="clear">
<view class="ts-x-bar ts-x-a" :style="{ background: textSoft }" />
<view class="ts-x-bar ts-x-b" :style="{ background: textSoft }" />
</view>
<view
v-if="showAction"
class="ts-go"
:style="{ color: primary, borderColor: hairline }"
@tap.stop="emitSearch"
>
{{ actionText }}
</view>
</view>
</template>
<script>
import { getActiveVars } from '@/utils/theme'
/**
* 全站搜索框。首页只作入口(disabled + 整条可点),列表/搜索页可输入。
* 形态跟装修 search 变体:bar / pill / line / overlay / float。
*/
export default {
name: 'ThemeSearch',
props: {
// Vue3 v-model;value 只作兼容,避免旧写法丢词
modelValue: { type: String, default: '' },
value: { type: String, default: '' },
placeholder: { type: String, default: '搜索心仪的产品型号' },
disabled: { type: Boolean, default: false },
showAction: { type: Boolean, default: true },
actionText: { type: String, default: '检索' },
variant: { type: String, default: 'bar' },
},
data() {
return { focused: false }
},
computed: {
keyword() {
return this.modelValue !== '' ? this.modelValue : this.value
},
vars() {
return getActiveVars() || {}
},
primary() {
return this.vars['--color-primary'] || '#B08D57'
},
surface() {
return this.vars['--color-surface'] || '#FFFFFF'
},
soft() {
return this.vars['--color-bg-soft'] || this.vars['--color-surface-soft'] || '#F6F1EA'
},
text() {
return this.vars['--color-text'] || '#1C1917'
},
textSoft() {
return this.vars['--color-text-soft'] || '#8A8178'
},
border() {
return this.vars['--color-border'] || '#E7DFD3'
},
hairline() {
return this.hexAlpha(this.primary, 0.28)
},
iconColor() {
return this.focused ? this.primary : this.textSoft
},
phStyle() {
return `color:${this.textSoft};font-size:26rpx;letter-spacing:1rpx;`
},
wrapStyle() {
const radius = this.variant === 'pill' ? '999rpx' : this.vars['--radius-md'] || '16rpx'
if (this.variant === 'line') {
return {
background: 'transparent',
borderColor: this.focused ? this.primary : this.border,
borderRadius: '0',
}
}
if (this.variant === 'overlay' || this.variant === 'float') {
return {
background: this.hexAlpha(this.surface, 0.92),
borderColor: this.focused ? this.primary : this.hairline,
borderRadius: radius,
}
}
return {
background: this.soft,
borderColor: this.focused ? this.primary : this.hairline,
borderRadius: radius,
}
},
},
methods: {
sync(next) {
this.$emit('update:modelValue', next)
this.$emit('input', next)
},
onInput(e) {
this.sync((e && e.detail && e.detail.value) || '')
},
emitSearch() {
this.$emit('search', this.keyword || '')
},
onWrapTap() {
if (this.disabled) {
this.$emit('click')
}
},
clear() {
this.sync('')
},
hexAlpha(hex, alpha) {
let raw = String(hex || '').replace('#', '')
if (raw.length === 3) {
raw = raw[0] + raw[0] + raw[1] + raw[1] + raw[2] + raw[2]
}
if (raw.length !== 6) {
return 'rgba(176,141,87,' + alpha + ')'
}
const n = parseInt(raw, 16)
const r = (n >> 16) & 255
const g = (n >> 8) & 255
const b = n & 255
return 'rgba(' + r + ',' + g + ',' + b + ',' + alpha + ')'
},
},
}
</script>
<style lang="scss" scoped>
.ts {
display: flex;
align-items: center;
height: 76rpx;
padding: 0 8rpx 0 24rpx;
border: 1rpx solid;
box-sizing: border-box;
}
.ts--line {
height: 72rpx;
padding: 0 4rpx 0 8rpx;
border-width: 0;
border-bottom-width: 1rpx;
}
.ts--float {
box-shadow: 0 12rpx 32rpx rgba(28, 25, 23, 0.06);
}
.ts-ico {
position: relative;
width: 28rpx;
height: 28rpx;
flex-shrink: 0;
margin-right: 16rpx;
}
.ts-ring {
width: 20rpx;
height: 20rpx;
border: 2rpx solid;
border-radius: 50%;
box-sizing: border-box;
}
.ts-stem {
position: absolute;
right: 0;
bottom: 1rpx;
width: 10rpx;
height: 2rpx;
border-radius: 2rpx;
transform: rotate(42deg);
transform-origin: left center;
}
.ts-field {
flex: 1;
min-width: 0;
height: 76rpx;
font-size: 26rpx;
letter-spacing: 1rpx;
background: transparent;
}
.ts-x {
position: relative;
width: 36rpx;
height: 36rpx;
flex-shrink: 0;
}
.ts-x-bar {
position: absolute;
left: 10rpx;
top: 17rpx;
width: 16rpx;
height: 2rpx;
border-radius: 2rpx;
}
.ts-x-a {
transform: rotate(45deg);
}
.ts-x-b {
transform: rotate(-45deg);
}
.ts-go {
flex-shrink: 0;
height: 52rpx;
margin-left: 8rpx;
padding: 0 20rpx;
border-left: 1rpx solid;
font-size: 22rpx;
letter-spacing: 4rpx;
line-height: 52rpx;
}
</style>