blob: f2aea72fe294f62223dbdbd9185a703acc81d90d [file] [log] [blame]
guangchao.xu070005a2020-12-07 09:56:40 +08001<template>
2 <view
3 class="u-input"
4 :class="{
5 'u-input--border': border,
6 'u-input--error': validateState
7 }"
8 :style="{
9 padding: `0 ${border ? 20 : 0}rpx`,
10 borderColor: borderColor,
11 textAlign: inputAlign
12 }"
13 @tap.stop="inputClick"
14 >
15 <textarea
16 v-if="type == 'textarea'"
17 class="u-input__input u-input__textarea"
18 :style="[getStyle]"
19 :value="defaultValue"
20 :placeholder="placeholder"
21 :placeholderStyle="placeholderStyle"
22 :disabled="disabled"
23 :maxlength="inputMaxlength"
24 :fixed="fixed"
25 :focus="focus"
26 :autoHeight="autoHeight"
27 :selection-end="uSelectionEnd"
28 :selection-start="uSelectionStart"
29 :cursor-spacing="getCursorSpacing"
30 :show-confirm-bar="showConfirmbar"
31 @input="handleInput"
32 @blur="handleBlur"
33 @focus="onFocus"
34 @confirm="onConfirm"
35 />
36 <input
37 v-else
38 class="u-input__input"
39 :type="type == 'password' ? 'text' : type"
40 :style="[getStyle]"
41 :value="defaultValue"
42 :password="type == 'password' && !showPassword"
43 :placeholder="placeholder"
44 :placeholderStyle="placeholderStyle"
45 :disabled="disabled || type === 'select'"
46 :maxlength="inputMaxlength"
47 :focus="focus"
48 :confirmType="confirmType"
49 :cursor-spacing="getCursorSpacing"
50 :selection-end="uSelectionEnd"
51 :selection-start="uSelectionStart"
52 :show-confirm-bar="showConfirmbar"
53 @focus="onFocus"
54 @blur="handleBlur"
55 @input="handleInput"
56 @confirm="onConfirm"
57 />
58 <view class="u-input__right-icon u-flex">
59 <view class="u-input__right-icon__clear u-input__right-icon__item" @tap="onClear" v-if="clearable && value != '' && focused">
60 <u-icon size="32" name="close-circle-fill" color="#c0c4cc"/>
61 </view>
62 <view class="u-input__right-icon__clear u-input__right-icon__item" v-if="passwordIcon && type == 'password'">
63 <u-icon size="32" :name="!showPassword ? 'eye' : 'eye-fill'" color="#c0c4cc" @click="showPassword = !showPassword"/>
64 </view>
65 <view class="u-input__right-icon--select u-input__right-icon__item" v-if="type == 'select'" :class="{
66 'u-input__right-icon--select--reverse': selectOpen
67 }">
68 <u-icon name="arrow-down-fill" size="26" color="#c0c4cc"></u-icon>
69 </view>
70 </view>
71 </view>
72</template>
73
74<script>
75import Emitter from '../../libs/util/emitter.js';
76
77/**
78 * input 输入框
79 * @description 此组件为一个输入框,默认没有边框和样式,是专门为配合表单组件u-form而设计的,利用它可以快速实现表单验证,输入内容,下拉选择等功能。
80 * @tutorial http://uviewui.com/components/input.html
81 * @property {String} type 模式选择,见官网说明
82 * @property {Boolean} clearable 是否显示右侧的清除图标(默认true)
83 * @property {} v-model 用于双向绑定输入框的值
84 * @property {String} input-align 输入框文字的对齐方式(默认left)
85 * @property {String} placeholder placeholder显示值(默认 '请输入内容')
86 * @property {Boolean} disabled 是否禁用输入框(默认false)
87 * @property {String Number} maxlength 输入框的最大可输入长度(默认140)
88 * @property {String Number} selection-start 光标起始位置,自动聚焦时有效,需与selection-end搭配使用(默认-1)
89 * @property {String Number} maxlength 光标结束位置,自动聚焦时有效,需与selection-start搭配使用(默认-1)
90 * @property {String Number} cursor-spacing 指定光标与键盘的距离,单位px(默认0)
91 * @property {String} placeholderStyle placeholder的样式,字符串形式,如"color: red;"(默认 "color: #c0c4cc;")
92 * @property {String} confirm-type 设置键盘右下角按钮的文字,仅在type为text时生效(默认done)
93 * @property {Object} custom-style 自定义输入框的样式,对象形式
94 * @property {Boolean} focus 是否自动获得焦点(默认false)
95 * @property {Boolean} fixed 如果type为textarea,且在一个"position:fixed"的区域,需要指明为true(默认false)
96 * @property {Boolean} password-icon type为password时,是否显示右侧的密码查看图标(默认true)
97 * @property {Boolean} border 是否显示边框(默认false)
98 * @property {String} border-color 输入框的边框颜色(默认#dcdfe6)
99 * @property {Boolean} auto-height 是否自动增高输入区域,type为textarea时有效(默认true)
100 * @property {String Number} height 高度,单位rpx(text类型时为70,textarea时为100)
101 * @example <u-input v-model="value" :type="type" :border="border" />
102 */
103export default {
104 name: 'u-input',
105 mixins: [Emitter],
106 props: {
107 value: {
108 type: [String, Number],
109 default: ''
110 },
111 // 输入框的类型,textarea,text,number
112 type: {
113 type: String,
114 default: 'text'
115 },
116 inputAlign: {
117 type: String,
118 default: 'left'
119 },
120 placeholder: {
121 type: String,
122 default: '请输入内容'
123 },
124 disabled: {
125 type: Boolean,
126 default: false
127 },
128 maxlength: {
129 type: [Number, String],
130 default: 140
131 },
132 placeholderStyle: {
133 type: String,
134 default: 'color: #c0c4cc;'
135 },
136 confirmType: {
137 type: String,
138 default: 'done'
139 },
140 // 输入框的自定义样式
141 customStyle: {
142 type: Object,
143 default() {
144 return {};
145 }
146 },
147 // 如果 textarea 是在一个 position:fixed 的区域,需要显示指定属性 fixed 为 true
148 fixed: {
149 type: Boolean,
150 default: false
151 },
152 // 是否自动获得焦点
153 focus: {
154 type: Boolean,
155 default: false
156 },
157 // 密码类型时,是否显示右侧的密码图标
158 passwordIcon: {
159 type: Boolean,
160 default: true
161 },
162 // input|textarea是否显示边框
163 border: {
164 type: Boolean,
165 default: false
166 },
167 // 输入框的边框颜色
168 borderColor: {
169 type: String,
170 default: '#dcdfe6'
171 },
172 autoHeight: {
173 type: Boolean,
174 default: true
175 },
176 // type=select时,旋转右侧的图标,标识当前处于打开还是关闭select的状态
177 // open-打开,close-关闭
178 selectOpen: {
179 type: Boolean,
180 default: false
181 },
182 // 高度,单位rpx
183 height: {
184 type: [Number, String],
185 default: ''
186 },
187 // 是否可清空
188 clearable: {
189 type: Boolean,
190 default: true
191 },
192 // 指定光标与键盘的距离,单位 px
193 cursorSpacing: {
194 type: [Number, String],
195 default: 0
196 },
197 // 光标起始位置,自动聚焦时有效,需与selection-end搭配使用
198 selectionStart: {
199 type: [Number, String],
200 default: -1
201 },
202 // 光标结束位置,自动聚焦时有效,需与selection-start搭配使用
203 selectionEnd: {
204 type: [Number, String],
205 default: -1
206 },
207 // 是否自动去除两端的空格
208 trim: {
209 type: Boolean,
210 default: true
211 },
212 // 是否显示键盘上方带有”完成“按钮那一栏
213 showConfirmbar:{
214 type:Boolean,
215 default:true
216 }
217 },
218 data() {
219 return {
220 defaultValue: this.value,
221 inputHeight: 70, // input的高度
222 textareaHeight: 100, // textarea的高度
223 validateState: false, // 当前input的验证状态,用于错误时,边框是否改为红色
224 focused: false, // 当前是否处于获得焦点的状态
225 showPassword: false, // 是否预览密码
226 lastValue: '', // 用于头条小程序,判断@input中,前后的值是否发生了变化,因为头条中文下,按下键没有输入内容,也会触发@input时间
227 };
228 },
229 watch: {
230 value(nVal, oVal) {
231 this.defaultValue = nVal;
232 // 当值发生变化,且为select类型时(此时input被设置为disabled,不会触发@input事件),模拟触发@input事件
233 if(nVal != oVal && this.type == 'select') this.handleInput({
234 detail: {
235 value: nVal
236 }
237 })
238 },
239 },
240 computed: {
241 // 因为uniapp的input组件的maxlength组件必须要数值,这里转为数值,给用户可以传入字符串数值
242 inputMaxlength() {
243 return Number(this.maxlength);
244 },
245 getStyle() {
246 let style = {};
247 // 如果没有自定义高度,就根据type为input还是textare来分配一个默认的高度
248 style.minHeight = this.height ? this.height + 'rpx' : this.type == 'textarea' ?
249 this.textareaHeight + 'rpx' : this.inputHeight + 'rpx';
250 style = Object.assign(style, this.customStyle);
251 return style;
252 },
253 //
254 getCursorSpacing() {
255 return Number(this.cursorSpacing);
256 },
257 // 光标起始位置
258 uSelectionStart() {
259 return String(this.selectionStart);
260 },
261 // 光标结束位置
262 uSelectionEnd() {
263 return String(this.selectionEnd);
264 }
265 },
266 created() {
267 // 监听u-form-item发出的错误事件,将输入框边框变红色
268 this.$on('on-form-item-error', this.onFormItemError);
269 },
270 methods: {
271 /**
272 * change 事件
273 * @param event
274 */
275 handleInput(event) {
276 let value = event.detail.value;
277 // 判断是否去除空格
278 if(this.trim) value = this.$u.trim(value);
279 // vue 原生的方法 return 出去
280 this.$emit('input', value);
281 // 当前model 赋值
282 this.defaultValue = value;
283 // 过一个生命周期再发送事件给u-form-item,否则this.$emit('input')更新了父组件的值,但是微信小程序上
284 // 尚未更新到u-form-item,导致获取的值为空,从而校验混论
285 // 这里不能延时时间太短,或者使用this.$nextTick,否则在头条上,会造成混乱
286 setTimeout(() => {
287 // 头条小程序由于自身bug,导致中文下,每按下一个键(尚未完成输入),都会触发一次@input,导致错误,这里进行判断处理
288 // #ifdef MP-TOUTIAO
289 if(this.$u.trim(value) == this.lastValue) return ;
290 this.lastValue = value;
291 // #endif
292 // 将当前的值发送到 u-form-item 进行校验
293 this.dispatch('u-form-item', 'on-form-change', value);
294 }, 40)
295 },
296 /**
297 * blur 事件
298 * @param event
299 */
300 handleBlur(event) {
301 // 最开始使用的是监听图标@touchstart事件,自从hx2.8.4后,此方法在微信小程序出错
302 // 这里改为监听点击事件,手点击清除图标时,同时也发生了@blur事件,导致图标消失而无法点击,这里做一个延时
303 setTimeout(() => {
304 this.focused = false;
305 }, 100)
306 // vue 原生的方法 return 出去
307 this.$emit('blur', event.detail.value);
308 setTimeout(() => {
309 // 头条小程序由于自身bug,导致中文下,每按下一个键(尚未完成输入),都会触发一次@input,导致错误,这里进行判断处理
310 // #ifdef MP-TOUTIAO
311 if(this.$u.trim(value) == this.lastValue) return ;
312 this.lastValue = value;
313 // #endif
314 // 将当前的值发送到 u-form-item 进行校验
315 this.dispatch('u-form-item', 'on-form-blur', event.detail.value);
316 }, 40)
317 },
318 onFormItemError(status) {
319 this.validateState = status;
320 },
321 onFocus(event) {
322 this.focused = true;
323 this.$emit('focus');
324 },
325 onConfirm(e) {
326 this.$emit('confirm', e.detail.value);
327 },
328 onClear(event) {
329 this.$emit('input', '');
330 },
331 inputClick() {
332 this.$emit('click');
333 }
334 }
335};
336</script>
337
338<style lang="scss" scoped>
339@import "../../libs/css/style.components.scss";
340
341.u-input {
342 position: relative;
343 flex: 1;
344 @include vue-flex;
345
346 &__input {
347 //height: $u-form-item-height;
348 font-size: 28rpx;
349 color: $u-main-color;
350 flex: 1;
351 }
352
353 &__textarea {
354 width: auto;
355 font-size: 28rpx;
356 color: $u-main-color;
357 padding: 10rpx 0;
358 line-height: normal;
359 flex: 1;
360 }
361
362 &--border {
363 border-radius: 6rpx;
364 border-radius: 4px;
365 border: 1px solid $u-form-item-border-color;
366 }
367
368 &--error {
369 border-color: $u-type-error!important;
370 }
371
372 &__right-icon {
373
374 &__item {
375 margin-left: 10rpx;
376 }
377
378 &--select {
379 transition: transform .4s;
380
381 &--reverse {
382 transform: rotate(-180deg);
383 }
384 }
385 }
386}
387</style>