358 lines
7.7 KiB
358 lines
7.7 KiB
10 months ago
|
<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>
|