Files
cmgd-mini-app/uni_modules/nutui-uni/components/searchbar/searchbar.vue
2026-01-05 12:47:14 +08:00

218 lines
5.7 KiB
Vue

<script setup lang="ts">
import type { InputOnBlurEvent, InputOnFocusEvent, InputOnInputEvent } from '@uni-helper/uni-app-types'
import type { CSSProperties } from 'vue'
import { computed, defineComponent, reactive, toRef, useSlots } from 'vue'
import { BLUR_EVENT, CHANGE_EVENT, CLEAR_EVENT, FOCUS_EVENT, PREFIX, SEARCH_EVENT, UPDATE_MODEL_EVENT } from '../_constants'
import { getMainClass, getMainStyle } from '../_utils'
import { useTranslate } from '../../locale'
import { useFormDisabled } from '../form/form'
import NutIcon from '../icon/icon.vue'
import { searchbarEmits, searchbarProps } from './searchbar'
const props = defineProps(searchbarProps)
const emit = defineEmits(searchbarEmits)
const slots = useSlots()
function hasSlot(name: string) {
return Boolean(slots[name])
}
const formDisabled = useFormDisabled(toRef(props, 'disabled'))
const state = reactive({
active: false,
})
function stringModelValue() {
if (props.modelValue == null)
return ''
return String(props.modelValue)
}
const innerValue = computed<string>(() => {
return stringModelValue()
})
const innerMaxLength = computed(() => {
if (props.maxLength == null)
return -1
return Number(props.maxLength)
})
const classes = computed(() => {
return getMainClass(props, componentName, {
'safe-area-inset-bottom': props.safeAreaInsetBottom,
})
})
const styles = computed(() => {
return getMainStyle(props, {
background: props.background,
})
})
const inputWrapperStyles = computed(() => {
const style: CSSProperties = {
background: props.inputBackground,
}
if (state.active)
Object.assign(style, props.focusStyle)
return style
})
const inputStyles = computed(() => {
return {
textAlign: props.inputAlign,
}
})
function handleValue(value: string) {
if (innerMaxLength.value > 0 && value.length > innerMaxLength.value)
value = value.slice(0, innerMaxLength.value)
return value
}
function handleInput(event: InputOnInputEvent) {
const value = handleValue(event.detail.value)
emit(UPDATE_MODEL_EVENT, value, event)
emit(CHANGE_EVENT, value, event)
}
function handleFocus(event: InputOnFocusEvent) {
const value = handleValue(event.detail.value)
state.active = true
emit(FOCUS_EVENT, value, event)
}
function handleBlur(event: InputOnBlurEvent) {
const value = handleValue(event.detail.value)
setTimeout(() => {
state.active = false
}, 200)
emit(BLUR_EVENT, value, event)
}
function handleClear(event: any) {
emit(UPDATE_MODEL_EVENT, '', event)
emit(CHANGE_EVENT, '', event)
emit(CLEAR_EVENT, '')
}
function handleSubmit() {
emit(SEARCH_EVENT, innerValue.value)
}
function handleInputClick(event: any) {
emit('clickInput', innerValue.value, event)
}
function handleLeftIconClick(event: any) {
emit('clickLeftIcon', innerValue.value, event)
}
function handleRightIconClick(event: any) {
emit('clickRightIcon', innerValue.value, event)
}
</script>
<script lang="ts">
const componentName = `${PREFIX}-searchbar`
const { translate } = useTranslate(componentName)
export default defineComponent({
name: componentName,
options: {
virtualHost: true,
addGlobalClass: true,
styleIsolation: 'shared',
},
})
</script>
<template>
<view :class="classes" :style="styles">
<view
v-if="hasSlot('leftout')"
class="nut-searchbar__search-icon nut-searchbar__left-search-icon"
@click="handleLeftIconClick"
>
<slot name="leftout" />
</view>
<view class="nut-searchbar__search-input" :class="[props.shape]" :style="inputWrapperStyles">
<view v-if="hasSlot('leftin')" class="nut-searchbar__search-icon nut-searchbar__iptleft-search-icon">
<slot name="leftin" />
</view>
<view class="nut-searchbar__input-inner" :class="{ 'nut-searchbar__input-inner-absolute': hasSlot('rightin') }">
<form
class="nut-searchbar__input-form"
action="#"
onsubmit="return false"
@submit.prevent="handleSubmit"
>
<input
class="nut-searchbar__input-bar"
:class="{ 'nut-searchbar__input-bar_clear': props.clearable }"
:style="inputStyles"
:type="props.inputType as any"
:maxlength="innerMaxLength"
:placeholder="props.placeholder || translate('placeholder')"
:value="innerValue"
:focus="props.autofocus"
:confirm-type="props.confirmType"
:disabled="formDisabled"
:readonly="props.readonly"
:cursor-spacing="props.cursorSpacing"
@click="handleInputClick"
@input="handleInput"
@focus="handleFocus"
@blur="handleBlur"
@confirm="handleSubmit"
>
</form>
</view>
<view
class="nut-searchbar__input-inner-icon"
:class="{ 'nut-searchbar__input-inner-icon-absolute': hasSlot('rightin') }"
>
<view
v-if="props.clearable"
class="nut-searchbar__search-icon nut-searchbar__input-clear"
:class="{ 'nut-hidden': innerValue.length <= 0 }"
@click="handleClear"
>
<template v-if="hasSlot('clear-icon')">
<slot name="clear-icon" />
</template>
<NutIcon v-else :name="props.clearIcon" />
</view>
<view
v-if="hasSlot('rightin')"
class="nut-searchbar__search-icon nut-searchbar__iptright-search-icon"
@click="handleRightIconClick"
>
<slot name="rightin" />
</view>
</view>
</view>
<view v-if="hasSlot('rightout')" class="nut-searchbar__search-icon nut-searchbar__right-search-icon">
<slot name="rightout" />
</view>
</view>
</template>
<style lang="scss">
@import "./index";
</style>