You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
271 lines
5.4 KiB
271 lines
5.4 KiB
<template> |
|
<view :class="inputClassName" ref="inputRef"> |
|
<input class="input" v-model="inputValue" @blur="handleBlur" @input="handleInput" :placeholder="props.placeholder" |
|
:type="props.type" @confirm="handleConfirm" /> |
|
|
|
<view class="messageBox" v-if="props.rules.required"> |
|
{{props.rules.message}} |
|
</view> |
|
|
|
<!-- 移除按钮 --> |
|
<view class="statusIconBox"> |
|
<view |
|
:class="{removeIcon:true, showClearable: inputClassName.success, removeClearable: !inputClassName.success}" |
|
v-if="clearable"> |
|
<u-icon :name="statusIcon" :color="inputClassName.success ?'#3AD8BC' : '#F8544B'" size="40"></u-icon> |
|
</view> |
|
</view> |
|
|
|
<view class="removeIconBox"> |
|
<view @click="()=>{ |
|
inputValue = '' |
|
handleChangeInputStatus() |
|
}" :class="{removeIcon:true, showClearable: inputValue, removeClearable: !inputValue}" v-if="clearable"> |
|
<u-icon name="close-circle-fill" color="#8f8f8f" size="40"></u-icon> |
|
</view> |
|
</view> |
|
</view> |
|
</template> |
|
|
|
<script setup lang="ts"> |
|
import { computed, defineProps, defineEmits, ref, nextTick, type PropType } from 'vue'; |
|
|
|
type rulesType = { |
|
/** 警示信息 */ |
|
message : string, |
|
/** 是否进行值的检测 */ |
|
required : boolean, |
|
/** 检测规则 */ |
|
rulesFn ?: Function, |
|
trigger : string | string[] |
|
} |
|
|
|
const props = defineProps({ |
|
/** 双向绑定的值 */ |
|
modelValue: { |
|
type: Number as PropType<Number> || String as PropType<String>, |
|
required: true |
|
}, |
|
/** 输入框类型 */ |
|
type: { |
|
type: String as PropType<String>, |
|
required: false, |
|
default: 'text' |
|
}, |
|
/** 验证规则 */ |
|
rules: { |
|
type: Object as PropType<rulesType>, |
|
required: false, |
|
default: { |
|
message: '', |
|
required: true |
|
} as any |
|
}, |
|
/** 是否显示清除按钮 */ |
|
clearable: { |
|
type: Boolean as PropType<Boolean>, |
|
required: false, |
|
default: true |
|
}, |
|
/** 提示 */ |
|
placeholder: { |
|
type: String as PropType<String>, |
|
required: false, |
|
default: '' |
|
}, |
|
/** 是否聚焦 */ |
|
focus: { |
|
type: Boolean as PropType<Boolean>, |
|
required: false, |
|
default: false |
|
} |
|
}) |
|
/** 输入框实例 */ |
|
const inputRef = ref() |
|
|
|
/** 状态Icon */ |
|
const statusIcon = ref('') |
|
|
|
/** 输入框类名 */ |
|
const inputClassName = ref({ |
|
input_container: true |
|
}) |
|
|
|
const emit = defineEmits(['input', 'change', 'blur', 'change', 'update:modelValue', 'confirm']) |
|
|
|
const inputValue = computed({ |
|
get() { |
|
return props.modelValue |
|
}, |
|
set(value) { |
|
emit('update:modelValue', value) |
|
} |
|
}) |
|
|
|
/** 输入框状态改变 */ |
|
const handleChangeInputStatus = async () => { |
|
if (!props.rules.required) return |
|
|
|
await nextTick() |
|
|
|
statusIcon.value = '' |
|
|
|
delete inputClassName.value.success |
|
delete inputClassName.value.error |
|
|
|
let isSuccess = false |
|
|
|
if (props.rules.rulesFn) isSuccess = props.rules.rulesFn(inputValue.value) |
|
else isSuccess = Boolean(inputValue.value || inputValue.value === 0) |
|
|
|
isSuccess ? inputClassName.value.success = true : inputClassName.value.error = true |
|
if (isSuccess) { |
|
inputClassName.value.success = true |
|
statusIcon.value = 'checkmark-circle-fill' |
|
} else { |
|
inputClassName.value.error = true |
|
statusIcon.value = 'info-circle-fill' |
|
} |
|
} |
|
|
|
/** 输入框值变化时 */ |
|
const handleChange = (newValue : string | number, oldValue : string | number) => { |
|
emit('change', newValue, oldValue) |
|
} |
|
|
|
/** 输入框失焦事件 */ |
|
const handleBlur = (event) => { |
|
emit('blur', event) |
|
handleChangeInputStatus() |
|
} |
|
|
|
/** 输入框事件 */ |
|
const handleInput = (event) => { |
|
emit('blur', event) |
|
handleChangeInputStatus() |
|
} |
|
|
|
const handleConfirm = (event) => { |
|
emit('confirm', event) |
|
} |
|
</script> |
|
|
|
<style scoped lang="scss"> |
|
@keyframes showIcon { |
|
from { |
|
transform: translate(-50%, -200%); |
|
opacity: 0; |
|
} |
|
|
|
to { |
|
transform: translate(-50%, -50%); |
|
opacity: 1; |
|
} |
|
} |
|
|
|
@keyframes romveIcon { |
|
from { |
|
transform: translate(-50%, -50%); |
|
opacity: 1; |
|
} |
|
|
|
to { |
|
transform: translate(-50%, -200%); |
|
opacity: 0; |
|
} |
|
} |
|
|
|
/** 动画时间 */ |
|
$animationTime: 0.3s; |
|
/** 验证成功颜色 */ |
|
$successColor: #3AD8BC; |
|
/** 验证失败颜色 */ |
|
$errorColor: #F8544B; |
|
|
|
.input_container { |
|
display: flex; |
|
position: relative; |
|
overflow: hidden; |
|
// height: 80upx; |
|
height: 100%; |
|
transition: all $animationTime; |
|
border: 4upx solid transparent; |
|
// background: #fff; |
|
border-radius: 10upx; |
|
|
|
&.success { |
|
border: 4upx solid $successColor; |
|
|
|
.removeIconBox { |
|
width: 60upx; |
|
} |
|
} |
|
|
|
&.error { |
|
border: 4upx solid $errorColor; |
|
|
|
.removeIconBox { |
|
width: 0upx; |
|
} |
|
} |
|
|
|
.removeIconBox { |
|
position: relative; |
|
height: 100%; |
|
width: 0upx; |
|
flex: none; |
|
transition: all $animationTime; |
|
overflow: hidden; |
|
|
|
.removeIcon { |
|
position: absolute; |
|
top: 50%; |
|
left: 50%; |
|
transform: translate(50%, -200%); |
|
|
|
&.showClearable { |
|
animation: showIcon $animationTime forwards; |
|
} |
|
|
|
&.removeClearable { |
|
animation: romveIcon $animationTime forwards; |
|
} |
|
} |
|
|
|
} |
|
|
|
.statusIconBox { |
|
position: relative; |
|
height: 100%; |
|
width: 60upx; |
|
flex: none; |
|
transition: all $animationTime; |
|
overflow: hidden; |
|
|
|
.removeIcon { |
|
position: absolute; |
|
top: 50%; |
|
left: 50%; |
|
transform: translate(-50%, -50%); |
|
|
|
:deep(.u-icon__icon) { |
|
color: $errorColor !important; |
|
transition: all $animationTime; |
|
} |
|
|
|
&.showClearable { |
|
:deep(.u-icon__icon) { |
|
color: $successColor !important; |
|
} |
|
|
|
} |
|
} |
|
} |
|
} |
|
|
|
.input { |
|
height: 100%; |
|
flex: 1; |
|
padding: 0 20upx; |
|
} |
|
</style> |