| <template> | 
|     <view class="uni-easyinput" :class="{ 'uni-easyinput-error': msg }" :style="boxStyle"> | 
|         <view class="uni-easyinput__content" :class="inputContentClass" :style="inputContentStyle"> | 
|             <uni-icons v-if="prefixIcon" class="content-clear-icon" :type="prefixIcon" color="#c0c4cc" | 
|                 @click="onClickIcon('prefix')" size="22"></uni-icons> | 
|             <textarea v-if="type === 'textarea'" class="uni-easyinput__content-textarea" | 
|                 :class="{ 'input-padding': inputBorder }" :name="name" :value="val" :placeholder="placeholder" | 
|                 :placeholderStyle="placeholderStyle" :disabled="disabled" | 
|                 placeholder-class="uni-easyinput__placeholder-class" :maxlength="inputMaxlength" :focus="focused" | 
|                 :autoHeight="autoHeight" :cursor-spacing="cursorSpacing" @input="onInput" @blur="_Blur" @focus="_Focus" | 
|                 @confirm="onConfirm" @keyboardheightchange="onkeyboardheightchange"></textarea> | 
|             <input v-else :type="type === 'password' ? 'text' : type" class="uni-easyinput__content-input" | 
|                 :style="inputStyle" :name="name" :value="val" :password="!showPassword && type === 'password'" | 
|                 :placeholder="placeholder" :placeholderStyle="placeholderStyle" | 
|                 placeholder-class="uni-easyinput__placeholder-class" :disabled="disabled" :maxlength="inputMaxlength" | 
|                 :focus="focused" :confirmType="confirmType" :cursor-spacing="cursorSpacing" @focus="_Focus" | 
|                 @blur="_Blur" @input="onInput" @confirm="onConfirm" @keyboardheightchange="onkeyboardheightchange" /> | 
|             <template v-if="type === 'password' && passwordIcon"> | 
|                 <!-- 开启密码时显示小眼睛 --> | 
|                 <uni-icons v-if="isVal" class="content-clear-icon" :class="{ 'is-textarea-icon': type === 'textarea' }" | 
|                     :type="showPassword ? 'eye-slash-filled' : 'eye-filled'" :size="22" | 
|                     :color="focusShow ? primaryColor : '#c0c4cc'" @click="onEyes"></uni-icons> | 
|             </template> | 
|             <template v-else-if="suffixIcon"> | 
|                 <uni-icons v-if="suffixIcon" class="content-clear-icon" :type="suffixIcon" color="#c0c4cc" | 
|                     @click="onClickIcon('suffix')" size="22"></uni-icons> | 
|             </template> | 
|             <template v-else> | 
|                 <uni-icons v-if="clearable && isVal && !disabled && type !== 'textarea'" class="content-clear-icon" | 
|                     :class="{ 'is-textarea-icon': type === 'textarea' }" type="clear" :size="clearSize" | 
|                     :color="msg ? '#dd524d' : focusShow ? primaryColor : '#c0c4cc'" @click="onClear"></uni-icons> | 
|             </template> | 
|             <slot name="right"></slot> | 
|             <view v-if="maxlength&&maxlength>0" class="input-number"> | 
|                 ({{val&&val.length||0}}/{{maxlength}}) | 
|             </view> | 
|         </view> | 
|     </view> | 
| </template> | 
|   | 
| <script> | 
|     /** | 
|      * Easyinput 输入框 | 
|      * @description 此组件可以实现表单的输入与校验,包括 "text" 和 "textarea" 类型。 | 
|      * @tutorial https://ext.dcloud.net.cn/plugin?id=3455 | 
|      * @property {String}    value    输入内容 | 
|      * @property {String }    type    输入框的类型(默认text) password/text/textarea/.. | 
|      *     @value text            文本输入键盘 | 
|      *     @value textarea    多行文本输入键盘 | 
|      *     @value password    密码输入键盘 | 
|      *     @value number        数字输入键盘,注意iOS上app-vue弹出的数字键盘并非9宫格方式 | 
|      *     @value idcard        身份证输入键盘,信、支付宝、百度、QQ小程序 | 
|      *     @value digit        带小数点的数字键盘    ,App的nvue页面、微信、支付宝、百度、头条、QQ小程序支持 | 
|      * @property {Boolean}    clearable    是否显示右侧清空内容的图标控件,点击可清空输入框内容(默认true) | 
|      * @property {Boolean}    autoHeight    是否自动增高输入区域,type为textarea时有效(默认true) | 
|      * @property {String }    placeholder    输入框的提示文字 | 
|      * @property {String }    placeholderStyle    placeholder的样式(内联样式,字符串),如"color: #ddd" | 
|      * @property {Boolean}    focus    是否自动获得焦点(默认false) | 
|      * @property {Boolean}    disabled    是否禁用(默认false) | 
|      * @property {Number }    maxlength    最大输入长度,设置为 -1 的时候不限制最大长度(默认140) | 
|      * @property {String }    confirmType    设置键盘右下角按钮的文字,仅在type="text"时生效(默认done) | 
|      * @property {Number }    clearSize    清除图标的大小,单位px(默认15) | 
|      * @property {String}    prefixIcon    输入框头部图标 | 
|      * @property {String}    suffixIcon    输入框尾部图标 | 
|      * @property {String}    primaryColor    设置主题色(默认#2979ff) | 
|      * @property {Boolean}    trim    是否自动去除两端的空格 | 
|      * @property {Boolean}    cursorSpacing    指定光标与键盘的距离,单位 px | 
|      * @value both    去除两端空格 | 
|      * @value left    去除左侧空格 | 
|      * @value right    去除右侧空格 | 
|      * @value start    去除左侧空格 | 
|      * @value end        去除右侧空格 | 
|      * @value all        去除全部空格 | 
|      * @value none    不去除空格 | 
|      * @property {Boolean}    inputBorder    是否显示input输入框的边框(默认true) | 
|      * @property {Boolean}    passwordIcon    type=password时是否显示小眼睛图标 | 
|      * @property {Object}    styles    自定义颜色 | 
|      * @event {Function}    input    输入框内容发生变化时触发 | 
|      * @event {Function}    focus    输入框获得焦点时触发 | 
|      * @event {Function}    blur    输入框失去焦点时触发 | 
|      * @event {Function}    confirm    点击完成按钮时触发 | 
|      * @event {Function}    iconClick    点击图标时触发 | 
|      * @example <uni-easyinput v-model="mobile"></uni-easyinput> | 
|      */ | 
|     function obj2strClass(obj) { | 
|         let classess = ''; | 
|         for (let key in obj) { | 
|             const val = obj[key]; | 
|             if (val) { | 
|                 classess += `${key} `; | 
|             } | 
|         } | 
|         return classess; | 
|     } | 
|   | 
|     function obj2strStyle(obj) { | 
|         let style = ''; | 
|         for (let key in obj) { | 
|             const val = obj[key]; | 
|             style += `${key}:${val};`; | 
|         } | 
|         return style; | 
|     } | 
|     export default { | 
|         name: 'uni-easyinput', | 
|         emits: ['click', 'iconClick', 'update:modelValue', 'input', 'focus', 'blur', 'confirm', 'clear', 'eyes', 'change', | 
|             'keyboardheightchange' | 
|         ], | 
|         model: { | 
|             prop: 'modelValue', | 
|             event: 'update:modelValue' | 
|         }, | 
|         options: { | 
|             virtualHost: true | 
|         }, | 
|         inject: { | 
|             form: { | 
|                 from: 'uniForm', | 
|                 default: null | 
|             }, | 
|             formItem: { | 
|                 from: 'uniFormItem', | 
|                 default: null | 
|             } | 
|         }, | 
|         props: { | 
|             name: String, | 
|             value: [Number, String], | 
|             modelValue: [Number, String], | 
|             type: { | 
|                 type: String, | 
|                 default: 'text' | 
|             }, | 
|             clearable: { | 
|                 type: Boolean, | 
|                 default: true | 
|             }, | 
|             autoHeight: { | 
|                 type: Boolean, | 
|                 default: false | 
|             }, | 
|             placeholder: { | 
|                 type: String, | 
|                 default: ' ' | 
|             }, | 
|             placeholderStyle: String, | 
|             focus: { | 
|                 type: Boolean, | 
|                 default: false | 
|             }, | 
|             disabled: { | 
|                 type: Boolean, | 
|                 default: false | 
|             }, | 
|             maxlength: { | 
|                 type: [Number, String], | 
|                 default: 140 | 
|             }, | 
|             confirmType: { | 
|                 type: String, | 
|                 default: 'done' | 
|             }, | 
|             clearSize: { | 
|                 type: [Number, String], | 
|                 default: 24 | 
|             }, | 
|             inputBorder: { | 
|                 type: Boolean, | 
|                 default: true | 
|             }, | 
|             prefixIcon: { | 
|                 type: String, | 
|                 default: '' | 
|             }, | 
|             suffixIcon: { | 
|                 type: String, | 
|                 default: '' | 
|             }, | 
|             trim: { | 
|                 type: [Boolean, String], | 
|                 default: false | 
|             }, | 
|             cursorSpacing: { | 
|                 type: Number, | 
|                 default: 0 | 
|             }, | 
|             passwordIcon: { | 
|                 type: Boolean, | 
|                 default: true | 
|             }, | 
|             primaryColor: { | 
|                 type: String, | 
|                 default: '#2979ff' | 
|             }, | 
|             styles: { | 
|                 type: Object, | 
|                 default () { | 
|                     return { | 
|                         color: '#333', | 
|                         backgroundColor: '#fff', | 
|                         disableColor: '#F7F6F6', | 
|                         borderColor: '#e5e5e5' | 
|                     }; | 
|                 } | 
|             }, | 
|             errorMessage: { | 
|                 type: [String, Boolean], | 
|                 default: '' | 
|             } | 
|         }, | 
|         data() { | 
|             return { | 
|                 focused: false, | 
|                 val: '', | 
|                 showMsg: '', | 
|                 border: false, | 
|                 isFirstBorder: false, | 
|                 showClearIcon: false, | 
|                 showPassword: false, | 
|                 focusShow: false, | 
|                 localMsg: '', | 
|                 isEnter: false // 用于判断当前是否是使用回车操作 | 
|             }; | 
|         }, | 
|         computed: { | 
|             // 输入框内是否有值 | 
|             isVal() { | 
|                 const val = this.val; | 
|                 // fixed by mehaotian 处理值为0的情况,字符串0不在处理范围 | 
|                 if (val || val === 0) { | 
|                     return true; | 
|                 } | 
|                 return false; | 
|             }, | 
|   | 
|             msg() { | 
|                 // console.log('computed', this.form, this.formItem); | 
|                 // if (this.form) { | 
|                 //     return this.errorMessage || this.formItem.errMsg; | 
|                 // } | 
|                 // TODO 处理头条 formItem 中 errMsg 不更新的问题 | 
|                 return this.localMsg || this.errorMessage; | 
|             }, | 
|             // 因为uniapp的input组件的maxlength组件必须要数值,这里转为数值,用户可以传入字符串数值 | 
|             inputMaxlength() { | 
|                 return Number(this.maxlength); | 
|             }, | 
|   | 
|             // 处理外层样式的style | 
|             boxStyle() { | 
|                 return `color:${this.inputBorder && this.msg ? '#e43d33' : this.styles.color};`; | 
|             }, | 
|             // input 内容的类和样式处理 | 
|             inputContentClass() { | 
|                 return obj2strClass({ | 
|                     'is-input-border': this.inputBorder, | 
|                     'is-input-error-border': this.inputBorder && this.msg, | 
|                     'is-textarea': this.type === 'textarea', | 
|                     'is-disabled': this.disabled, | 
|                     'is-focused': this.focusShow | 
|                 }); | 
|             }, | 
|             inputContentStyle() { | 
|                 const focusColor = this.focusShow ? this.primaryColor : this.styles.borderColor; | 
|                 const borderColor = this.inputBorder && this.msg ? '#dd524d' : focusColor; | 
|                 return obj2strStyle({ | 
|                     'border-color': borderColor || '#e5e5e5', | 
|                     'background-color': this.disabled ? this.styles.disableColor : this.styles.backgroundColor | 
|                 }); | 
|             }, | 
|             // input右侧样式 | 
|             inputStyle() { | 
|                 const paddingRight = this.type === 'password' || this.clearable || this.prefixIcon ? '' : '10px'; | 
|                 return obj2strStyle({ | 
|                     'padding-right': paddingRight, | 
|                     'padding-left': this.prefixIcon ? '' : '10px' | 
|                 }); | 
|             } | 
|         }, | 
|         watch: { | 
|             value(newVal) { | 
|                 this.val = newVal; | 
|             }, | 
|             modelValue(newVal) { | 
|                 this.val = newVal; | 
|             }, | 
|             focus(newVal) { | 
|                 this.$nextTick(() => { | 
|                     this.focused = this.focus; | 
|                     this.focusShow = this.focus; | 
|                 }); | 
|             } | 
|         }, | 
|         created() { | 
|             this.init(); | 
|             // TODO 处理头条vue3 computed 不监听 inject 更改的问题(formItem.errMsg) | 
|             if (this.form && this.formItem) { | 
|                 this.$watch('formItem.errMsg', newVal => { | 
|                     this.localMsg = newVal; | 
|                 }); | 
|             } | 
|         }, | 
|         mounted() { | 
|             this.$nextTick(() => { | 
|                 this.focused = this.focus; | 
|                 this.focusShow = this.focus; | 
|             }); | 
|         }, | 
|         methods: { | 
|             /** | 
|              * 初始化变量值 | 
|              */ | 
|             init() { | 
|                 if (this.value || this.value === 0) { | 
|                     this.val = this.value; | 
|                 } else if (this.modelValue || this.modelValue === 0 || this.modelValue === '') { | 
|                     this.val = this.modelValue; | 
|                 } else { | 
|                     this.val = null; | 
|                 } | 
|             }, | 
|   | 
|             /** | 
|              * 点击图标时触发 | 
|              * @param {Object} type | 
|              */ | 
|             onClickIcon(type) { | 
|                 this.$emit('iconClick', type); | 
|             }, | 
|   | 
|             /** | 
|              * 显示隐藏内容,密码框时生效 | 
|              */ | 
|             onEyes() { | 
|                 this.showPassword = !this.showPassword; | 
|                 this.$emit('eyes', this.showPassword); | 
|             }, | 
|   | 
|             /** | 
|              * 输入时触发 | 
|              * @param {Object} event | 
|              */ | 
|             onInput(event) { | 
|                 let value = event.detail.value; | 
|                 // 判断是否去除空格 | 
|                 if (this.trim) { | 
|                     if (typeof this.trim === 'boolean' && this.trim) { | 
|                         value = this.trimStr(value); | 
|                     } | 
|                     if (typeof this.trim === 'string') { | 
|                         value = this.trimStr(value, this.trim); | 
|                     } | 
|                 } | 
|                 if (this.errMsg) this.errMsg = ''; | 
|                 this.val = value; | 
|                 // TODO 兼容 vue2 | 
|                 this.$emit('input', value); | 
|                 // TODO 兼容 vue3 | 
|                 this.$emit('update:modelValue', value); | 
|             }, | 
|   | 
|             /** | 
|              * 外部调用方法 | 
|              * 获取焦点时触发 | 
|              * @param {Object} event | 
|              */ | 
|             onFocus() { | 
|                 this.$nextTick(() => { | 
|                     this.focused = true; | 
|                 }); | 
|                 this.$emit('focus', null); | 
|             }, | 
|   | 
|             _Focus(event) { | 
|                 this.focusShow = true; | 
|                 this.$emit('focus', event); | 
|             }, | 
|   | 
|             /** | 
|              * 外部调用方法 | 
|              * 失去焦点时触发 | 
|              * @param {Object} event | 
|              */ | 
|             onBlur() { | 
|                 this.focused = false; | 
|                 this.$emit('focus', null); | 
|             }, | 
|             _Blur(event) { | 
|                 let value = event.detail.value; | 
|                 this.focusShow = false; | 
|                 this.$emit('blur', event); | 
|                 // 根据类型返回值,在event中获取的值理论上讲都是string | 
|                 if (this.isEnter === false) { | 
|                     this.$emit('change', this.val); | 
|                 } | 
|                 // 失去焦点时参与表单校验 | 
|                 if (this.form && this.formItem) { | 
|                     const { | 
|                         validateTrigger | 
|                     } = this.form; | 
|                     if (validateTrigger === 'blur') { | 
|                         this.formItem.onFieldChange(); | 
|                     } | 
|                 } | 
|             }, | 
|   | 
|             /** | 
|              * 按下键盘的发送键 | 
|              * @param {Object} e | 
|              */ | 
|             onConfirm(e) { | 
|                 this.$emit('confirm', this.val); | 
|                 this.isEnter = true; | 
|                 this.$emit('change', this.val); | 
|                 this.$nextTick(() => { | 
|                     this.isEnter = false; | 
|                 }); | 
|             }, | 
|   | 
|             /** | 
|              * 清理内容 | 
|              * @param {Object} event | 
|              */ | 
|             onClear(event) { | 
|                 this.val = ''; | 
|                 // TODO 兼容 vue2 | 
|                 this.$emit('input', ''); | 
|                 // TODO 兼容 vue2 | 
|                 // TODO 兼容 vue3 | 
|                 this.$emit('update:modelValue', ''); | 
|                 // 点击叉号触发 | 
|                 this.$emit('clear'); | 
|             }, | 
|   | 
|             /** | 
|              * 键盘高度发生变化的时候触发此事件 | 
|              * 兼容性:微信小程序2.7.0+、App 3.1.0+ | 
|              * @param {Object} event | 
|              */ | 
|             onkeyboardheightchange(event) { | 
|                 this.$emit("keyboardheightchange", event); | 
|             }, | 
|   | 
|             /** | 
|              * 去除空格 | 
|              */ | 
|             trimStr(str, pos = 'both') { | 
|                 if (pos === 'both') { | 
|                     return str.trim(); | 
|                 } else if (pos === 'left') { | 
|                     return str.trimLeft(); | 
|                 } else if (pos === 'right') { | 
|                     return str.trimRight(); | 
|                 } else if (pos === 'start') { | 
|                     return str.trimStart(); | 
|                 } else if (pos === 'end') { | 
|                     return str.trimEnd(); | 
|                 } else if (pos === 'all') { | 
|                     return str.replace(/\s+/g, ''); | 
|                 } else if (pos === 'none') { | 
|                     return str; | 
|                 } | 
|                 return str; | 
|             } | 
|         } | 
|     }; | 
| </script> | 
|   | 
| <style lang="scss"> | 
|     $uni-error: #e43d33; | 
|     $uni-border-1: #dcdfe6 !default; | 
|   | 
|     .uni-easyinput { | 
|         /* #ifndef APP-NVUE */ | 
|         width: 100%; | 
|         /* #endif */ | 
|         flex: 1; | 
|         position: relative; | 
|         text-align: left; | 
|         color: #333; | 
|         font-size: 14px; | 
|     } | 
|   | 
|     .uni-easyinput__content { | 
|         flex: 1; | 
|         /* #ifndef APP-NVUE */ | 
|         width: 100%; | 
|         display: flex; | 
|         box-sizing: border-box; | 
|         // min-height: 36px; | 
|         /* #endif */ | 
|         flex-direction: row; | 
|         align-items: center; | 
|         // 处理border动画刚开始显示黑色的问题 | 
|         border-color: #fff; | 
|         transition-property: border-color; | 
|         transition-duration: 0.3s; | 
|         position: relative; | 
|   | 
|         .input-number { | 
|             position: absolute; | 
|             bottom: 12rpx; | 
|             right: 24rpx; | 
|             color: #CCCCCC; | 
|             font-size: 24rpx; | 
|         } | 
|     } | 
|   | 
|     .uni-easyinput__content-input { | 
|         /* #ifndef APP-NVUE */ | 
|         width: auto; | 
|         /* #endif */ | 
|         position: relative; | 
|         overflow: hidden; | 
|         flex: 1; | 
|         line-height: 1; | 
|         font-size: 14px; | 
|         height: 35px; | 
|         // min-height: 36px; | 
|     } | 
|   | 
|     .uni-easyinput__placeholder-class { | 
|         color: #999; | 
|         font-size: 12px; | 
|         // font-weight: 200; | 
|     } | 
|   | 
|     .is-textarea { | 
|         align-items: flex-start; | 
|     } | 
|   | 
|     .is-textarea-icon { | 
|         margin-top: 5px; | 
|     } | 
|   | 
|     .uni-easyinput__content-textarea { | 
|         position: relative; | 
|         overflow: hidden; | 
|         flex: 1; | 
|         line-height: 1.5; | 
|         font-size: 14px; | 
|         margin: 6px; | 
|         margin-left: 0; | 
|         height: 80px; | 
|         min-height: 80px; | 
|         /* #ifndef APP-NVUE */ | 
|         min-height: 80px; | 
|         width: auto; | 
|         /* #endif */ | 
|     } | 
|   | 
|     .input-padding { | 
|         padding-left: 10px; | 
|         padding: 18rpx 24rpx; | 
|     } | 
|   | 
|     .content-clear-icon { | 
|         padding: 0 5px; | 
|     } | 
|   | 
|     .label-icon { | 
|         margin-right: 5px; | 
|         margin-top: -1px; | 
|     } | 
|   | 
|     // 显示边框 | 
|     .is-input-border { | 
|         /* #ifndef APP-NVUE */ | 
|         display: flex; | 
|         box-sizing: border-box; | 
|         /* #endif */ | 
|         flex-direction: row; | 
|         align-items: center; | 
|         border: 1px solid $uni-border-1; | 
|         border-radius: 4px; | 
|         /* #ifdef MP-ALIPAY */ | 
|         overflow: hidden; | 
|         /* #endif */ | 
|     } | 
|   | 
|     .uni-error-message { | 
|         position: absolute; | 
|         bottom: -17px; | 
|         left: 0; | 
|         line-height: 12px; | 
|         color: $uni-error; | 
|         font-size: 12px; | 
|         text-align: left; | 
|     } | 
|   | 
|     .uni-error-msg--boeder { | 
|         position: relative; | 
|         bottom: 0; | 
|         line-height: 22px; | 
|     } | 
|   | 
|     .is-input-error-border { | 
|         border-color: $uni-error; | 
|   | 
|         .uni-easyinput__placeholder-class { | 
|             color: mix(#fff, $uni-error, 50%); | 
|         } | 
|     } | 
|   | 
|     .uni-easyinput--border { | 
|         margin-bottom: 0; | 
|         padding: 10px 15px; | 
|         // padding-bottom: 0; | 
|         border-top: 1px #eee solid; | 
|     } | 
|   | 
|     .uni-easyinput-error { | 
|         padding-bottom: 0; | 
|     } | 
|   | 
|     .is-first-border { | 
|         /* #ifndef APP-NVUE */ | 
|         border: none; | 
|         /* #endif */ | 
|         /* #ifdef APP-NVUE */ | 
|         border-width: 0; | 
|         /* #endif */ | 
|     } | 
|   | 
|     .is-disabled { | 
|         background-color: #f7f6f6; | 
|         color: #d5d5d5; | 
|   | 
|         .uni-easyinput__placeholder-class { | 
|             color: #d5d5d5; | 
|             font-size: 12px; | 
|         } | 
|     } | 
| </style> |