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.
358 lines
7.7 KiB
358 lines
7.7 KiB
<template> |
|
<view :class="inputClassName" ref="inputRef"> |
|
<input class="input" v-model="inputValue" @blur="handleBlur" @input="handleInput" :placeholder="props.placeholder" |
|
:type="inputType" @confirm="handleConfirm" /> |
|
|
|
<view :class="{'messageBox': true, 'showMessage': inputClassName.error}" v-if="props.rules.required"> |
|
{{props.rules.message}} |
|
</view> |
|
|
|
<!-- 状态 --> |
|
<view class="statusIconBox" |
|
:style="{width: (props.type=== 'password' && props.showPwd) || isShowStatusIcon ? '30px': '0px'}"> |
|
<view |
|
:class="{statusIcon:true, showClearable: inputClassName.success, removeClearable: !inputClassName.success}" |
|
v-if="isShowStatusIcon"> |
|
<u-icon :name="statusIcon" :color="inputClassName.success ?'#3AD8BC' : '#F8544B'" size="40"></u-icon> |
|
</view> |
|
|
|
<!-- 是否显示密码Icon --> |
|
<view :class="{statusIcon:true}" v-if="props.type=== 'password' && props.showPwd" |
|
@click="isShowPwd = !isShowPwd"> |
|
<u-icon :name="isShowPwd?'eye-fill' :'eye-off' " color="#8f8f8f" size="40"></u-icon> |
|
</view> |
|
</view> |
|
|
|
<!-- 移除按钮 --> |
|
<view :class="{removeIconBox: true, show: !!props.modelValue || props.modelValue === 0}" v-if="clearable"> |
|
<view @click="()=>{ |
|
inputValue = ''; |
|
}" :class="`removeIcon ${!!props.modelValue || props.modelValue === 0 ? 'showClearable' : 'removeClearable'}`"> |
|
<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, watch, getCurrentInstance } from 'vue'; |
|
import utils from '@/utils/utils.js'; |
|
|
|
// vue实例 |
|
const instance = getCurrentInstance() |
|
|
|
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: false |
|
} as any |
|
}, |
|
/** 是否显示清除按钮 */ |
|
clearable: { |
|
type: Boolean as PropType<Boolean>, |
|
required: false, |
|
default: false |
|
}, |
|
/** 提示 */ |
|
placeholder: { |
|
type: String as PropType<String>, |
|
required: false, |
|
default: '' |
|
}, |
|
/** 是否聚焦 */ |
|
focus: { |
|
type: Boolean as PropType<Boolean>, |
|
required: false, |
|
default: false |
|
}, |
|
/** 是否显示状态Icon */ |
|
StatusIcon: { |
|
type: Boolean as PropType<Boolean>, |
|
required: false, |
|
default: false |
|
}, |
|
/** 是否显示密码Icon */ |
|
showPwd: { |
|
type: Boolean as PropType<Boolean>, |
|
required: false, |
|
default: false |
|
}, |
|
/** 是否显示边框 */ |
|
border: { |
|
type: Boolean as PropType<Boolean>, |
|
required: false, |
|
default: true |
|
}, |
|
/** 当输入框类型为number -- 最低值 */ |
|
min: { |
|
type: Number as PropType<Number>, |
|
required: false, |
|
default: -Infinity |
|
}, |
|
/** 当输入框类型为number -- 最大值 */ |
|
max: { |
|
type: Number as PropType<Number>, |
|
required: false, |
|
default: Infinity |
|
} |
|
}) |
|
/** 输入框实例 */ |
|
const inputRef = ref() |
|
|
|
/** 状态Icon */ |
|
const statusIcon = ref('') |
|
|
|
/** 输入框类名 */ |
|
const inputClassName = ref({ |
|
input_container: true, |
|
'border_none': !props.border |
|
}) |
|
|
|
/** 是否显示密码 */ |
|
const isShowPwd = ref(false) |
|
|
|
const emit = defineEmits(['input', 'change', 'blur', 'change', 'update:modelValue', 'confirm']) |
|
|
|
// 是否显示状态按钮 |
|
const isShowStatusIcon = computed(() => { |
|
if (props.type === 'password') return !props.showPwd && props.StatusIcon |
|
else return props.StatusIcon |
|
}) |
|
|
|
// 输入框类型 |
|
const inputType = computed(() => { |
|
const _type = props.type |
|
|
|
if (_type === 'password') { return isShowPwd.value ? 'text' : _type } |
|
else return _type |
|
}) |
|
|
|
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) |
|
|
|
console.log('isSuccess :>> ', isSuccess); |
|
|
|
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 handleBlur = async (event) => { |
|
emit('blur', event) |
|
|
|
// if (props.type === 'number') { |
|
// await nextTick() |
|
// setTimeout(() => { |
|
// try { |
|
// const _value = Number(inputValue.value) |
|
|
|
// console.log('inputValue.value :>> ', inputValue.value); |
|
// if (!utils.isNumber(_value) || !inputValue.value) return emit('update:modelValue', 0) |
|
|
|
|
|
// if (_value < props.min) return emit('update:modelValue', props.min) |
|
|
|
// if (_value > props.max) return emit('update:modelValue', props.max) |
|
|
|
// emit('update:modelValue', _value) |
|
// } catch (err) { |
|
// console.log('err :>> ', err); |
|
// //TODO handle the exception |
|
// } finally { |
|
// instance.proxy.$forceUpdate(); |
|
// console.log('instance :>> ', instance); |
|
// } |
|
|
|
|
|
// }, 100) |
|
|
|
// if (_value) |
|
// } |
|
} |
|
|
|
/** 输入框事件 */ |
|
const handleInput = (event) => { |
|
emit('input', event) |
|
} |
|
|
|
const handleConfirm = (event) => { |
|
emit('confirm', event) |
|
} |
|
|
|
watch(() => props.modelValue, () => { |
|
console.log('props.modelValue :>> ', props.modelValue); |
|
handleChangeInputStatus() |
|
}) |
|
</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: 100%; |
|
transition: all $animationTime; |
|
border: 4upx solid #fff; |
|
border-radius: 10upx; |
|
|
|
&.success { |
|
border: 4upx solid $successColor; |
|
} |
|
|
|
&.error { |
|
border: 4upx solid $errorColor; |
|
} |
|
|
|
&.border_none { |
|
border: none; |
|
} |
|
|
|
.removeIconBox { |
|
position: relative; |
|
height: 100%; |
|
width: 0upx; |
|
flex: none; |
|
transition: all $animationTime; |
|
overflow: hidden; |
|
|
|
&.show { |
|
width: 60upx; |
|
} |
|
|
|
.removeIcon { |
|
position: absolute; |
|
top: 50%; |
|
left: 50%; |
|
transform: translate(50%, -200%); |
|
animation: romveIcon $animationTime forwards; |
|
|
|
&.showClearable { |
|
animation: showIcon $animationTime forwards; |
|
} |
|
|
|
// &.removeClearable {} |
|
} |
|
|
|
} |
|
|
|
.statusIconBox { |
|
position: relative; |
|
height: 100%; |
|
width: 60upx; |
|
flex: none; |
|
transition: all $animationTime; |
|
// overflow: hidden; |
|
|
|
.statusIcon { |
|
position: absolute; |
|
top: 50%; |
|
left: 50%; |
|
transform: translate(-50%, -50%); |
|
} |
|
} |
|
} |
|
|
|
.messageBox { |
|
position: absolute; |
|
top: 105%; |
|
z-index: 99; |
|
color: var(--errColor); |
|
font-size: 0.8rem; |
|
opacity: 0; |
|
transition: all 0.3s; |
|
|
|
&.showMessage { |
|
opacity: 1; |
|
} |
|
} |
|
|
|
.input { |
|
height: 100%; |
|
flex: 1; |
|
padding: 0 20upx; |
|
} |
|
</style> |