yunshangxie/tuniao-ui/components/tn-input/tn-input.vue

448 lines
12 KiB
Vue
Raw Normal View History

2023-12-25 17:56:30 +08:00
<template>
<view
class="tn-input-class tn-input"
:class="{
'tn-input--border': border,
'tn-input--error': validateState
}"
:style="{
padding: `0 ${border ? 20 : 0}rpx`,
borderColor: borderColor,
textAlign: inputAlign
}"
@tap.stop="inputClick"
>
<textarea
v-if="type === 'textarea'"
class="tn-input__input tn-input__textarea"
:style="[inputStyle]"
:value="defaultValue"
:placeholder="placeholder"
:placeholderStyle="placeholderStyle"
:disabled="disabled || type === 'select'"
:maxlength="maxLength"
:fixed="fixed"
:focus="focus"
:autoHeight="autoHeight"
:selectionStart="elSelectionStart"
:selectionEnd="elSelectionEnd"
:cursorSpacing="cursorSpacing"
:showConfirmBar="showConfirmBar"
@input="handleInput"
@blur="handleBlur"
@focus="onFocus"
@confirm="onConfirm"
/>
<view v-else>
<view
v-if="type === 'select'"
class="tn-input__text"
>
{{defaultValue}}
</view>
<input
v-else
class="tn-input__input"
:type="type === 'password' ? 'text' : type"
:style="[inputStyle]"
:value="defaultValue"
:password="type === 'password' && !showPassword"
:placeholder="placeholder"
:placeholderStyle="placeholderStyle"
:disabled="disabled || type === 'select'"
:maxlength="maxLength"
:focus="focus"
:confirmType="confirmType"
:selectionStart="elSelectionStart"
:selectionEnd="elSelectionEnd"
:cursorSpacing="cursorSpacing"
:showConfirmBar="showConfirmBar"
@input="handleInput"
@blur="handleBlur"
@focus="onFocus"
@confirm="onConfirm"
/>
</view>
<!-- 右边的icon -->
<view class="tn-input__right-icon tn-flex tn-flex-col-center">
<!-- 清除按钮 -->
<view
v-if="clearable && value !== '' && focused"
class="tn-input__right-icon__item tn-input__right-icon__clear"
@tap="onClear"
>
<view class="icon tn-icon-close"></view>
</view>
<view
v-else-if="type === 'text' && !focused && showRightIcon && rightIcon !== ''"
class="tn-input__right-icon__item tn-input__right-icon__clear"
>
<view class="icon" :class="[`tn-icon-${rightIcon}`]"></view>
</view>
<!-- 显示密码按钮 -->
<view
v-if="passwordIcon && type === 'password'"
class="tn-input__right-icon__item tn-input__right-icon__clear"
@tap="showPassword = !showPassword"
>
<view v-if="!showPassword" class="tn-icon-eye-hide"></view>
<view v-else class="icon tn-icon-eye"></view>
</view>
<!-- 可选项箭头 -->
<view
v-if="type === 'select'"
class="tn-input__right-icon__item tn-input__right-icon__select"
:class="{
'tn-input__right-icon__select--reverse': selectOpen
}"
>
<view class="icon tn-icon-up-triangle"></view>
</view>
</view>
</view>
</template>
<script>
import Emitter from '../../libs/utils/emitter.js'
export default {
mixins: [Emitter],
name: 'tn-input',
props: {
value: {
type: [String, Number],
default: ''
},
// 输入框的类型
type: {
type: String,
default: 'text'
},
// 输入框文字对齐方式
inputAlign: {
type: String,
default: 'left'
},
// 文本框为空时显示的信息
placeholder: {
type: String,
default: ''
},
placeholderStyle: {
type: String,
default: 'color: #AAAAAA'
},
// 是否禁用输入框
disabled: {
type: Boolean,
default: false
},
// 可输入文字的最大长度
maxLength: {
type: Number,
default: 255
},
// 输入框高度
height: {
type: Number,
default: 0
},
// 根据内容自动调整高度
autoHeight: {
type: Boolean,
default: true
},
// 键盘右下角显示的文字仅在text时生效
confirmType: {
type: String,
default: 'done'
},
// 输入框自定义样式
customStyle: {
type: Object,
default() {
return {}
}
},
// 是否固定输入框
fixed: {
type: Boolean,
default: false
},
// 是否自动获取焦点
focus: {
type: Boolean,
default: false
},
// 当type为password时是否显示右侧密码图标
passwordIcon: {
type: Boolean,
default: true
},
// 当type为 input或者textarea时是否显示边框
border: {
type: Boolean,
default: false
},
// 边框的颜色
borderColor: {
type: String,
default: '#dcdfe6'
},
// 当type为select时旋转右侧图标标记当时select是打开还是关闭
selectOpen: {
type: Boolean,
default: false
},
// 是否可清空
clearable: {
type: Boolean,
default: true
},
// 光标与键盘的距离
cursorSpacing: {
type: Number,
default: 0
},
// selectionStart和selectionEnd需要搭配使用自动聚焦时生效
// 光标起始位置
selectionStart: {
type: Number,
default: -1
},
// 光标结束位置
selectionEnd: {
type: Number,
default: -1
},
// 自动去除两端空格
trim: {
type: Boolean,
default: true
},
// 是否显示键盘上方的完成按钮
showConfirmBar: {
type: Boolean,
default: true
},
// 是否在输入框内最右边显示图标
showRightIcon: {
type: Boolean,
default: false
},
// 最右边图标的名称
rightIcon: {
type: String,
default: ''
}
},
computed: {
// 输入框样式
inputStyle() {
let style = {}
// 如果没有设置高度,根据不同的类型设置一个默认值
style.minHeight = this.height ? this.height + 'rpx' :
this.type === 'textarea' ? this.textareaHeight + 'rpx' : this.inputHeight + 'rpx'
style = Object.assign(style, this.customStyle)
return style
},
// 光标起始位置
elSelectionStart() {
return String(this.selectionStart)
},
// 光标结束位置
elSelectionEnd() {
return String(this.selectionEnd)
}
},
data() {
return {
// 默认值
defaultValue: this.value,
// 输入框高度
inputHeight: 70,
// textarea的高度
textareaHeight: 100,
// 标记验证的状态
validateState: false,
// 标记是否获取到焦点
focused: false,
// 是否预览密码
showPassword: false,
// 用于头条小程序,判断@input中前后的值是否发生了变化因为头条中文下按下键没有输入内容也会触发@input事件
lastValue: '',
}
},
watch: {
value(newVal, oldVal) {
this.defaultValue = newVal
// 当值发生变化时并且type为select时不会触发input事件
// 模拟input事件
if (newVal !== oldVal && this.type === 'select') {
this.handleInput({
detail: {
value: newVal
}
})
}
}
},
created() {
// 监听form-item发出的错误事件将输入框变成红色
this.$on("on-form-item-error", this.onFormItemError)
},
methods: {
/**
* input事件
*/
handleInput(event) {
let value = event.detail.value
// 是否需要去掉空格
if (this.trim) value = this.$tn.string.trim(value)
// 原生事件
this.$emit('input', value)
// model赋值
this.defaultValue = value
// 过一个生命周期再发送事件给tn-form-item否则this.$emit('input')更新了父组件的值,但是微信小程序上
// 尚未更新到tn-form-item导致获取的值为空从而校验混论
// 这里不能延时时间太短或者使用this.$nextTick否则在头条上会造成混乱
setTimeout(() => {
// 头条小程序由于自身bug导致中文下每按下一个键(尚未完成输入),都会触发一次@input导致错误这里进行判断处理
// #ifdef MP-TOUTIAO
if (this.$tn.string.trim(value) === this.lastValue) return
this.lastValue = value
// #endif
// 发送当前的值到form-item进行校验
this.dispatch('tn-form-item','on-form-change', value)
}, 40)
},
/**
* blur事件
*/
handleBlur(event) {
let value = event.detail.value
// 由于点击清除图标也会触发blur事件导致图标消失从而无法点击
setTimeout(() => {
this.focused = false
}, 100)
// 原生事件
this.$emit('blur', value)
// 过一个生命周期再发送事件给tn-form-item否则this.$emit('blur')更新了父组件的值,但是微信小程序上
// 尚未更新到tn-form-item导致获取的值为空从而校验混论
// 这里不能延时时间太短或者使用this.$nextTick否则在头条上会造成混乱
setTimeout(() => {
// 头条小程序由于自身bug导致中文下每按下一个键(尚未完成输入),都会触发一次@input导致错误这里进行判断处理
// #ifdef MP-TOUTIAO
if (this.$tn.string.trim(value) === this.lastValue) return
this.lastValue = value
// #endif
// 发送当前的值到form-item进行校验
this.dispatch('tn-form-item','on-form-blur', value)
}, 40)
},
// 处理校验错误
onFormItemError(status) {
this.validateState = status
},
// 聚焦事件
onFocus(event) {
this.focused = true
this.$emit('focus')
},
// 点击确认按钮事件
onConfirm(event) {
this.$emit('confirm', event.detail.value)
},
// 清除事件
onClear(event) {
this.$emit('input', '')
},
// 点击事件
inputClick() {
this.$emit('click')
}
}
}
</script>
<style lang="scss" scoped>
.tn-input {
display: flex;
flex-direction: row;
position: relative;
flex: 1;
&__input {
font-size: 28rpx;
color: $tn-font-color;
flex: 1;
}
&__text {
font-size: 28rpx;
color: $tn-font-color;
flex: 1;
min-width: 296rpx;
max-width: 100%;
text-overflow:clip;
}
&__textarea {
width: auto;
font-size: 28rpx;
color: $tn-font-color;
padding: 10rpx 0;
line-height: normal;
flex: 1;
}
&--border {
border-radius: 6rpx;
border: 2rpx solid $tn-border-solid-color;
}
&--error {
border-color: $tn-color-red !important;
}
&__right-icon {
line-height: 1;
.icon {
color: $tn-font-sub-color;
}
&__item {
margin-left: 10rpx;
}
&__clear {
.icon {
font-size: 32rpx;
}
}
&__select {
transition: transform .4s;
.icon {
font-size: 26rpx;
}
&--reverse {
transform: rotate(-180deg);
}
}
}
}
</style>