u-input.vue 14.6 KB
   1
   2
   3
   4
   5
   6
   7
   8
   9
  10
  11
  12
  13
  14
  15
  16
  17
  18
  19
  20
  21
  22
  23
  24
  25
  26
  27
  28
  29
  30
  31
  32
  33
  34
  35
  36
  37
  38
  39
  40
  41
  42
  43
  44
  45
  46
  47
  48
  49
  50
  51
  52
  53
  54
  55
  56
  57
  58
  59
  60
  61
  62
  63
  64
  65
  66
  67
  68
  69
  70
  71
  72
  73
  74
  75
  76
  77
  78
  79
  80
  81
  82
  83
  84
  85
  86
  87
  88
  89
  90
  91
  92
  93
  94
  95
  96
  97
  98
  99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 140
 141
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 179
 180
 181
 182
 183
 184
 185
 186
 187
 188
 189
 190
 191
 192
 193
 194
 195
 196
 197
 198
 199
 200
 201
 202
 203
 204
 205
 206
 207
 208
 209
 210
 211
 212
 213
 214
 215
 216
 217
 218
 219
 220
 221
 222
 223
 224
 225
 226
 227
 228
 229
 230
 231
 232
 233
 234
 235
 236
 237
 238
 239
 240
 241
 242
 243
 244
 245
 246
 247
 248
 249
 250
 251
 252
 253
 254
 255
 256
 257
 258
 259
 260
 261
 262
 263
 264
 265
 266
 267
 268
 269
 270
 271
 272
 273
 274
 275
 276
 277
 278
 279
 280
 281
 282
 283
 284
 285
 286
 287
 288
 289
 290
 291
 292
 293
 294
 295
 296
 297
 298
 299
 300
 301
 302
 303
 304
 305
 306
 307
 308
 309
 310
 311
 312
 313
 314
 315
 316
 317
 318
 319
 320
 321
 322
 323
 324
 325
 326
 327
 328
 329
 330
 331
 332
 333
 334
 335
 336
 337
 338
 339
 340
 341
 342
 343
 344
 345
 346
 347
 348
 349
 350
 351
 352
 353
 354
<template>
<view class="u-input" :class="inputClass" :style="[wrapperStyle]">
<view class="u-input__content">
<view
class="u-input__content__prefix-icon"
v-if="prefixIcon || $slots.prefix"
>
<slot name="prefix">
<u-icon
:name="prefixIcon"
size="18"
:customStyle="prefixIconStyle"
></u-icon>
</slot>
</view>
<view class="u-input__content__field-wrapper" @tap="clickHandler">
<!-- 根据uni-app的input组件文档,H5和APP中只要声明了password参数(无论true还是false),type均失效,此时
为了防止type=number时,又存在password属性,type无效,此时需要设置password为undefined
-->
<input
class="u-input__content__field-wrapper__field"
:style="[inputStyle]"
:type="type"
:focus="focus"
:cursor="cursor"
:value="innerValue"
:auto-blur="autoBlur"
:disabled="disabled || readonly"
:maxlength="maxlength"
:placeholder="placeholder"
:placeholder-style="placeholderStyle"
:placeholder-class="placeholderClass"
:confirm-type="confirmType"
:confirm-hold="confirmHold"
:hold-keyboard="holdKeyboard"
:cursor-spacing="cursorSpacing"
:adjust-position="adjustPosition"
:selection-end="selectionEnd"
:selection-start="selectionStart"
:password="password || type === 'password' || false"
:ignoreCompositionEvent="ignoreCompositionEvent"
@input="onInput"
@blur="onBlur"
@focus="onFocus"
@confirm="onConfirm"
@keyboardheightchange="onkeyboardheightchange"
/>
</view>
<view
class="u-input__content__clear"
v-if="isShowClear"
@tap="onClear"
>
<u-icon
name="close"
size="11"
color="#ffffff"
customStyle="line-height: 12px"
></u-icon>
</view>
<view
class="u-input__content__subfix-icon"
v-if="suffixIcon || $slots.suffix"
>
<slot name="suffix">
<u-icon
:name="suffixIcon"
size="18"
:customStyle="suffixIconStyle"
></u-icon>
</slot>
</view>
</view>
</view>
</template>

<script>
import props from "./props.js";
/**
* Input 输入框
* @description 此组件为一个输入框,默认没有边框和样式,是专门为配合表单组件u-form而设计的,利用它可以快速实现表单验证,输入内容,下拉选择等功能。
* @tutorial https://uviewui.com/components/input.html
* @property {String | Number} value 输入的值
* @property {String} type 输入框类型,见上方说明 ( 默认 'text' )
* @property {Boolean} fixed 如果 textarea 是在一个 position:fixed 的区域,需要显示指定属性 fixed 为 true,兼容性:微信小程序、百度小程序、字节跳动小程序、QQ小程序 ( 默认 false )
* @property {Boolean} disabled 是否禁用输入框 ( 默认 false )
* @property {String} disabledColor 禁用状态时的背景色( 默认 '#f5f7fa' )
* @property {Boolean} clearable 是否显示清除控件 ( 默认 false )
* @property {Boolean} password 是否密码类型 ( 默认 false )
* @property {String | Number} maxlength 最大输入长度,设置为 -1 的时候不限制最大长度 ( 默认 -1 )
* @property {String} placeholder 输入框为空时的占位符
* @property {String} placeholderClass 指定placeholder的样式类,注意页面或组件的style中写了scoped时,需要在类名前写/deep/ ( 默认 'input-placeholder' )
* @property {String | Object} placeholderStyle 指定placeholder的样式,字符串/对象形式,如"color: red;"
* @property {Boolean} showWordLimit 是否显示输入字数统计,只在 type ="text"或type ="textarea"时有效 ( 默认 false )
* @property {String} confirmType 设置右下角按钮的文字,兼容性详见uni-app文档 ( 默认 'done' )
* @property {Boolean} confirmHold 点击键盘右下角按钮时是否保持键盘不收起,H5无效 ( 默认 false )
* @property {Boolean} holdKeyboard focus时,点击页面的时候不收起键盘,微信小程序有效 ( 默认 false )
* @property {Boolean} focus 自动获取焦点,在 H5 平台能否聚焦以及软键盘是否跟随弹出,取决于当前浏览器本身的实现。nvue 页面不支持,需使用组件的 focus()、blur() 方法控制焦点 ( 默认 false )
* @property {Boolean} autoBlur 键盘收起时,是否自动失去焦点,目前仅App3.0.0+有效 ( 默认 false )
* @property {Boolean} disableDefaultPadding 是否去掉 iOS 下的默认内边距,仅微信小程序,且type=textarea时有效 ( 默认 false )
* @property {String | Number} cursor 指定focus时光标的位置( 默认 -1 )
* @property {String | Number} cursorSpacing 输入框聚焦时底部与键盘的距离 ( 默认 30 )
* @property {String | Number} selectionStart 光标起始位置,自动聚集时有效,需与selection-end搭配使用 ( 默认 -1 )
* @property {String | Number} selectionEnd 光标结束位置,自动聚集时有效,需与selection-start搭配使用 ( 默认 -1 )
* @property {Boolean} adjustPosition 键盘弹起时,是否自动上推页面 ( 默认 true )
* @property {String} inputAlign 输入框内容对齐方式( 默认 'left' )
* @property {String | Number} fontSize 输入框字体的大小 ( 默认 '15px' )
* @property {String} color 输入框字体颜色 ( 默认 '#303133' )
* @property {Function} formatter 内容式化函数
* @property {String} prefixIcon 输入框前置图标
* @property {String | Object} prefixIconStyle 前置图标样式,对象或字符串
* @property {String} suffixIcon 输入框后置图标
* @property {String | Object} suffixIconStyle 后置图标样式,对象或字符串
* @property {String} border 边框类型,surround-四周边框,bottom-底部边框,none-无边框 ( 默认 'surround' )
* @property {Boolean} readonly 是否只读,与disabled不同之处在于disabled会置灰组件,而readonly则不会 ( 默认 false )
* @property {String} shape 输入框形状,circle-圆形,square-方形 ( 默认 'square' )
* @property {Object} customStyle 定义需要用到的外部样式
* @property {Boolean} ignoreCompositionEvent 是否忽略组件内对文本合成系统事件的处理。
* @example <u-input v-model="value" :password="true" suffix-icon="lock-fill" />
*/
export default {
name: "u-input",
mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
data() {
return {
// 输入框的值
innerValue: "",
// 是否处于获得焦点状态
focused: false,
// value是否第一次变化,在watch中,由于加入immediate属性,会在第一次触发,此时不应该认为value发生了变化
firstChange: true,
// value绑定值的变化是由内部还是外部引起的
changeFromInner: false,
// 过滤处理方法
innerFormatter: value => value
};
},
watch: {
value: {
immediate: true,
handler(newVal, oldVal) {
this.innerValue = newVal;
/* #ifdef H5 */
// 在H5中,外部value变化后,修改input中的值,不会触发@input事件,此时手动调用值变化方法
if (
this.firstChange === false &&
this.changeFromInner === false
) {
this.valueChange();
}
/* #endif */
this.firstChange = false;
// 重置changeFromInner的值为false,标识下一次引起默认为外部引起的
this.changeFromInner = false;
},
},
},
computed: {
// 是否显示清除控件
isShowClear() {
const { clearable, readonly, focused, innerValue } = this;
return !!clearable && !readonly && !!focused && innerValue !== "";
},
// 组件的类名
inputClass() {
let classes = [],
{ border, disabled, shape } = this;
border === "surround" &&
(classes = classes.concat(["u-border", "u-input--radius"]));
classes.push(`u-input--${shape}`);
border === "bottom" &&
(classes = classes.concat([
"u-border-bottom",
"u-input--no-radius",
]));
return classes.join(" ");
},
// 组件的样式
wrapperStyle() {
const style = {};
// 禁用状态下,被背景色加上对应的样式
if (this.disabled) {
style.backgroundColor = this.disabledColor;
}
// 无边框时,去除内边距
if (this.border === "none") {
style.padding = "0";
} else {
// 由于uni-app的iOS开发者能力有限,导致需要分开写才有效
style.paddingTop = "6px";
style.paddingBottom = "6px";
style.paddingLeft = "9px";
style.paddingRight = "9px";
}
return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle));
},
// 输入框的样式
inputStyle() {
const style = {
color: this.color,
fontSize: uni.$u.addUnit(this.fontSize),
textAlign: this.inputAlign
};
return style;
},
},
methods: {
// 在微信小程序中,不支持将函数当做props参数,故只能通过ref形式调用
setFormatter(e) {
this.innerFormatter = e
},
// 当键盘输入时,触发input事件
onInput(e) {
let { value = "" } = e.detail || {};
// 格式化过滤方法
const formatter = this.formatter || this.innerFormatter
const formatValue = formatter(value)
// 为了避免props的单向数据流特性,需要先将innerValue值设置为当前值,再在$nextTick中重新赋予设置后的值才有效
this.innerValue = value
this.$nextTick(() => {
this.innerValue = formatValue;
this.valueChange();
})
},
// 输入框失去焦点时触发
onBlur(event) {
this.$emit("blur", event.detail.value);
// H5端的blur会先于点击清除控件的点击click事件触发,导致focused
// 瞬间为false,从而隐藏了清除控件而无法被点击到
uni.$u.sleep(50).then(() => {
this.focused = false;
});
// 尝试调用u-form的验证方法
uni.$u.formValidate(this, "blur");
},
// 输入框聚焦时触发
onFocus(event) {
this.focused = true;
this.$emit("focus");
},
// 点击完成按钮时触发
onConfirm(event) {
this.$emit("confirm", this.innerValue);
},
// 键盘高度发生变化的时候触发此事件
// 兼容性:微信小程序2.7.0+、App 3.1.0+
onkeyboardheightchange() {
this.$emit("keyboardheightchange");
},
// 内容发生变化,进行处理
valueChange() {
const value = this.innerValue;
this.$nextTick(() => {
this.$emit("input", value);
// 标识value值的变化是由内部引起的
this.changeFromInner = true;
this.$emit("change", value);
// 尝试调用u-form的验证方法
uni.$u.formValidate(this, "change");
});
},
// 点击清除控件
onClear() {
this.innerValue = "";
this.$nextTick(() => {
this.valueChange();
this.$emit("clear");
});
},
/**
* 在安卓nvue上,事件无法冒泡
* 在某些时间,我们希望监听u-from-item的点击事件,此时会导致点击u-form-item内的u-input后
* 无法触发u-form-item的点击事件,这里通过手动调用u-form-item的方法进行触发
*/
clickHandler() {
// #ifdef APP-NVUE
if (uni.$u.os() === "android") {
const formItem = uni.$u.$parent.call(this, "u-form-item");
if (formItem) {
formItem.clickHandler();
}
}
// #endif
},
},
};
</script>

<style lang="scss" scoped>
@import "../../libs/css/components.scss";

.u-input {
@include flex(row);
align-items: center;
justify-content: space-between;
flex: 1;

&--radius,
&--square {
border-radius: 4px;
}

&--no-radius {
border-radius: 0;
}

&--circle {
border-radius: 100px;
}

&__content {
flex: 1;
@include flex(row);
align-items: center;
justify-content: space-between;

&__field-wrapper {
position: relative;
@include flex(row);
margin: 0;
flex: 1;
&__field {
line-height: 26px;
text-align: left;
color: $u-main-color;
height: 24px;
font-size: 15px;
flex: 1;
}
}

&__clear {
width: 20px;
height: 20px;
border-radius: 100px;
background-color: #c6c7cb;
@include flex(row);
align-items: center;
justify-content: center;
transform: scale(0.82);
margin-left: 4px;
}

&__subfix-icon {
margin-left: 4px;
}

&__prefix-icon {
margin-right: 4px;
}
}
}
</style>