u-input.vue 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. <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="value"
  20. :placeholder="placeholder"
  21. :placeholderStyle="placeholderStyle"
  22. :disabled="disabled"
  23. :maxlength="inputMaxlength"
  24. :fixed="fixed"
  25. :focus="focus"
  26. :autoHeight="autoHeight"
  27. @input="handleInput"
  28. @blur="handleBlur"
  29. @focus="onFocus"
  30. @confirm="onConfirm"
  31. />
  32. <input
  33. v-else
  34. class="u-input__input"
  35. :type="type == 'password' ? 'text' : type"
  36. :style="[getStyle]"
  37. :value="defaultValue"
  38. :password="type == 'password' && showPassword"
  39. :placeholder="placeholder"
  40. :placeholderStyle="placeholderStyle"
  41. :disabled="disabled || type === 'select'"
  42. :maxlength="inputMaxlength"
  43. :focus="focus"
  44. :confirmType="confirmType"
  45. @focus="onFocus"
  46. @blur="handleBlur"
  47. @input="handleInput"
  48. @confirm="onConfirm"
  49. />
  50. <view class="u-input__right-icon u-flex">
  51. <view class="u-input__right-icon__clear u-input__right-icon__item" v-if="clearable && value && focused">
  52. <u-icon size="32" name="close-circle-fill" color="#c0c4cc" @touchstart="onClear"/>
  53. </view>
  54. <view class="u-input__right-icon__clear u-input__right-icon__item" v-if="passwordIcon && type == 'password'">
  55. <u-icon size="32" :name="showPassword ? 'eye' : 'eye-fill'" color="#c0c4cc" @click="showPassword = !showPassword"/>
  56. </view>
  57. <view class="u-input__right-icon--select u-input__right-icon__item" v-if="type == 'select'" :class="{
  58. 'u-input__right-icon--select--reverse': selectOpen
  59. }">
  60. <u-icon name="arrow-down-fill" size="26" color="#c0c4cc"></u-icon>
  61. </view>
  62. </view>
  63. </view>
  64. </template>
  65. <script>
  66. import Emitter from '../../libs/util/emitter.js';
  67. export default {
  68. name: 'u-input',
  69. mixins: [Emitter],
  70. props: {
  71. value: {
  72. type: String,
  73. default: ''
  74. },
  75. // 输入框的类型,textarea,text,number
  76. type: {
  77. type: String,
  78. default: 'text'
  79. },
  80. clearable: {
  81. type: Boolean,
  82. default: true
  83. },
  84. inputAlign: {
  85. type: String,
  86. default: 'left'
  87. },
  88. placeholder: {
  89. type: String,
  90. default: '请输入内容'
  91. },
  92. disabled: {
  93. type: Boolean,
  94. default: false
  95. },
  96. maxlength: {
  97. type: [Number, String],
  98. default: 140
  99. },
  100. placeholderStyle: {
  101. type: String,
  102. default: 'color: #c0c4cc;'
  103. },
  104. confirmType: {
  105. type: String,
  106. default: 'done'
  107. },
  108. // 输入框的自定义样式
  109. customStyle: {
  110. type: Object,
  111. default() {
  112. return {};
  113. }
  114. },
  115. // 如果 textarea 是在一个 position:fixed 的区域,需要显示指定属性 fixed 为 true
  116. fixed: {
  117. type: Boolean,
  118. default: false
  119. },
  120. // 是否自动获得焦点
  121. focus: {
  122. type: Boolean,
  123. default: false
  124. },
  125. // 密码类型时,是否显示右侧的密码图标
  126. passwordIcon: {
  127. type: Boolean,
  128. default: true
  129. },
  130. // input|textarea是否显示边框
  131. border: {
  132. type: Boolean,
  133. default: false
  134. },
  135. // 输入框的边框颜色
  136. borderColor: {
  137. type: String,
  138. default: '#dcdfe6'
  139. },
  140. autoHeight: {
  141. type: Boolean,
  142. default: true
  143. },
  144. // type=select时,旋转右侧的图标,标识当前处于打开还是关闭select的状态
  145. // open-打开,close-关闭
  146. selectOpen: {
  147. type: Boolean,
  148. default: false
  149. },
  150. // 高度,单位rpx
  151. height: {
  152. type: [Number, String],
  153. default: ''
  154. },
  155. // 是否可清空
  156. clearable: {
  157. type: Boolean,
  158. default: true
  159. },
  160. },
  161. data() {
  162. return {
  163. defaultValue: this.value,
  164. inputHeight: 70, // input的高度
  165. textareaHeight: 100, // textarea的高度
  166. validateState: false, // 当前input的验证状态,用于错误时,边框是否改为红色
  167. focused: false, // 当前是否处于获得焦点的状态
  168. showPassword: this.passwordIcon, // 是否预览密码
  169. marginRight: 0, // 输入框右边的距离,当获得焦点时各一个后面的距离,避免点击右边图标误触输入框
  170. };
  171. },
  172. watch: {
  173. value(nVal, oVal) {
  174. this.defaultValue = nVal;
  175. // 当值发生变化,且为select类型时(此时input被设置为disabled,不会触发@input事件),模拟触发@input事件
  176. if(nVal != oVal && this.type == 'select') this.handleInput({
  177. detail: {
  178. value: nVal
  179. }
  180. })
  181. },
  182. focused(nVal) {
  183. if(this.clearable && this.value) {
  184. this.getMarginRight();
  185. }
  186. }
  187. },
  188. computed: {
  189. // 因为uniapp的input组件的maxlength组件必须要数值,这里转为数值,给用户可以传入字符串数值
  190. inputMaxlength() {
  191. return Number(this.maxlength);
  192. },
  193. getStyle() {
  194. let style = {};
  195. // 如果没有自定义高度,就根据type为input还是textare来分配一个默认的高度
  196. style.minHeight = this.height ? this.height + 'rpx' : this.type == 'textarea' ?
  197. this.textareaHeight + 'rpx' : this.inputHeight + 'rpx';
  198. style.marginRight = this.marginRight + 'px';
  199. style = Object.assign(style, this.customStyle);
  200. return style;
  201. }
  202. },
  203. created() {
  204. // 监听u-form-item发出的错误事件,将输入框边框变红色
  205. this.$on('on-form-item-error', this.onFormItemError);
  206. },
  207. mounted() {
  208. this.getMarginRight();
  209. },
  210. methods: {
  211. // 计算输入框的右边距
  212. getMarginRight() {
  213. this.$nextTick(() => {
  214. this.$uGetRect('.u-input__right-icon').then(res => {
  215. // 此处20rpx为图标绝对定位右侧的“right”
  216. this.marginRight = res.width + uni.upx2px(20);
  217. })
  218. })
  219. },
  220. /**
  221. * change 事件
  222. * @param event
  223. */
  224. handleInput(event) {
  225. // 当前model 赋值
  226. this.defaultValue = event.detail.value;
  227. // vue 原生的方法 return 出去
  228. this.$emit('input', event.detail.value);
  229. // 过一个生命周期再发送事件给u-form-item,否则this.$emit('input')更新了父组件的值,但是微信小程序上
  230. // 尚未更新到u-form-item,导致获取的值为空,从而校验混论
  231. this.$nextTick(() => {
  232. // 将当前的值发送到 u-form-item 进行校验
  233. this.dispatch('u-form-item', 'on-form-change', event.detail.value);
  234. });
  235. },
  236. /**
  237. * blur 事件
  238. * @param event
  239. */
  240. handleBlur(event) {
  241. this.focused = false;
  242. // vue 原生的方法 return 出去
  243. this.$emit('blur', event.detail.value);
  244. this.$nextTick(() => {
  245. // 将当前的值发送到 u-form-item 进行校验
  246. this.dispatch('u-form-item', 'on-form-blur', event.detail.value);
  247. });
  248. },
  249. onFormItemError(status) {
  250. this.validateState = status;
  251. },
  252. onFocus(event) {
  253. this.focused = true;
  254. this.$emit('focus');
  255. },
  256. onConfirm(e) {
  257. this.$emit('confirm', e.detail.value);
  258. },
  259. onClear(event) {
  260. this.$emit('input');
  261. },
  262. inputClick() {
  263. this.$emit('click');
  264. }
  265. }
  266. };
  267. </script>
  268. <style lang="scss" scoped>
  269. .u-input {
  270. position: relative;
  271. flex: 1;
  272. &__input {
  273. //height: $u-form-item-height;
  274. font-size: 28rpx;
  275. color: $u-main-color;
  276. }
  277. &__textarea {
  278. width: auto;
  279. font-size: 28rpx;
  280. color: $u-main-color;
  281. padding: 10rpx 0;
  282. line-height: normal;
  283. }
  284. &--border {
  285. border-radius: 6rpx;
  286. border-radius: 4px;
  287. border: 1px solid $u-form-item-border-color;
  288. }
  289. &--error {
  290. border-color: $u-type-error!important;
  291. }
  292. &__right-icon {
  293. position: absolute;
  294. right: 20rpx;
  295. top: 50%;
  296. z-index: 3;
  297. transform: translateY(-50%);
  298. &__item {
  299. margin-left: 10rpx;
  300. }
  301. &--select {
  302. transition: transform .4s;
  303. &--reverse {
  304. transform: rotate(-180deg);
  305. }
  306. }
  307. }
  308. }
  309. </style>