You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

266 lines
7.7 KiB

4 months ago
  1. <template>
  2. <view class="u-code-input">
  3. <view
  4. class="u-code-input__item"
  5. :style="[itemStyle(index)]"
  6. v-for="(item, index) in codeLength"
  7. :key="index"
  8. >
  9. <view
  10. class="u-code-input__item__dot"
  11. v-if="dot && codeArray.length > index"
  12. ></view>
  13. <text
  14. v-else
  15. :style="{
  16. fontSize: addUnit(fontSize),
  17. fontWeight: bold ? 'bold' : 'normal',
  18. color: color
  19. }"
  20. >{{codeArray[index]}}</text>
  21. <view
  22. class="u-code-input__item__line"
  23. v-if="mode === 'line'"
  24. :style="[lineStyle]"
  25. ></view>
  26. <!-- #ifndef APP-PLUS -->
  27. <view v-if="isFocus && codeArray.length === index" :style="{backgroundColor: color}" class="u-code-input__item__cursor"></view>
  28. <!-- #endif -->
  29. </view>
  30. <input
  31. :disabled="disabledKeyboard"
  32. type="number"
  33. :focus="focus"
  34. :value="inputValue"
  35. :maxlength="maxlength"
  36. :adjustPosition="adjustPosition"
  37. class="u-code-input__input"
  38. @input="inputHandler"
  39. :style="{
  40. height: addUnit(size)
  41. }"
  42. @focus="isFocus = true"
  43. @blur="isFocus = false"
  44. />
  45. </view>
  46. </template>
  47. <script>
  48. import { props } from './props';
  49. import { mpMixin } from '../../libs/mixin/mpMixin';
  50. import { mixin } from '../../libs/mixin/mixin';
  51. import { addUnit, getPx } from '../../libs/function/index';
  52. /**
  53. * CodeInput 验证码输入
  54. * @description 该组件一般用于验证用户短信验证码的场景也可以结合uview-plus的键盘组件使用
  55. * @tutorial https://ijry.github.io/uview-plus/components/codeInput.html
  56. * @property {String | Number} maxlength 最大输入长度 默认 6
  57. * @property {Boolean} dot 是否用圆点填充 默认 false
  58. * @property {String} mode 显示模式box-盒子模式line-底部横线模式 默认 'box'
  59. * @property {Boolean} hairline 是否细边框 默认 false
  60. * @property {String | Number} space 字符间的距离 默认 10
  61. * @property {String | Number} value 预置值
  62. * @property {Boolean} focus 是否自动获取焦点 默认 false
  63. * @property {Boolean} bold 字体和输入横线是否加粗 默认 false
  64. * @property {String} color 字体颜色 默认 '#606266'
  65. * @property {String | Number} fontSize 字体大小单位px 默认 18
  66. * @property {String | Number} size 输入框的大小宽等于高 默认 35
  67. * @property {Boolean} disabledKeyboard 是否隐藏原生键盘如果想用自定义键盘的话需设置此参数为true 默认 false
  68. * @property {String} borderColor 边框和线条颜色 默认 '#c9cacc'
  69. * @property {Boolean} disabledDot 是否禁止输入"."符号 默认 true
  70. *
  71. * @event {Function} change 输入内容发生改变时触发具体见上方说明 value当前输入的值
  72. * @event {Function} finish 输入字符个数达maxlength值时触发见上方说明 value当前输入的值
  73. * @example <u-code-input v-model="value4" :focus="true"></u-code-input>
  74. */
  75. export default {
  76. name: 'u-code-input',
  77. mixins: [mpMixin, mixin, props],
  78. data() {
  79. return {
  80. inputValue: '',
  81. isFocus: this.focus
  82. }
  83. },
  84. watch: {
  85. // #ifdef VUE2
  86. value: {
  87. // #endif
  88. // #ifdef VUE3
  89. modelValue: {
  90. // #endif
  91. immediate: true,
  92. handler(val) {
  93. // 转为字符串,超出部分截掉
  94. this.inputValue = String(val).substring(0, this.maxlength)
  95. }
  96. },
  97. },
  98. computed: {
  99. // 根据长度,循环输入框的个数,因为头条小程序数值不能用于v-for
  100. codeLength() {
  101. return new Array(Number(this.maxlength))
  102. },
  103. // 循环item的样式
  104. itemStyle() {
  105. return index => {
  106. const style = {
  107. width: addUnit(this.size),
  108. height: addUnit(this.size)
  109. }
  110. // 盒子模式下,需要额外进行处理
  111. if (this.mode === 'box') {
  112. // 设置盒子的边框,如果是细边框,则设置为0.5px宽度
  113. style.border = `${this.hairline ? 0.5 : 1}px solid ${this.borderColor}`
  114. // 如果盒子间距为0的话
  115. if (getPx(this.space) === 0) {
  116. // 给第一和最后一个盒子设置圆角
  117. if (index === 0) {
  118. style.borderTopLeftRadius = '3px'
  119. style.borderBottomLeftRadius = '3px'
  120. }
  121. if (index === this.codeLength.length - 1) {
  122. style.borderTopRightRadius = '3px'
  123. style.borderBottomRightRadius = '3px'
  124. }
  125. // 最后一个盒子的右边框需要保留
  126. if (index !== this.codeLength.length - 1) {
  127. style.borderRight = 'none'
  128. }
  129. }
  130. }
  131. if (index !== this.codeLength.length - 1) {
  132. // 设置验证码字符之间的距离,通过margin-right设置,最后一个字符,无需右边框
  133. style.marginRight = addUnit(this.space)
  134. } else {
  135. // 最后一个盒子的有边框需要保留
  136. style.marginRight = 0
  137. }
  138. return style
  139. }
  140. },
  141. // 将输入的值,转为数组,给item历遍时,根据当前的索引显示数组的元素
  142. codeArray() {
  143. return String(this.inputValue).split('')
  144. },
  145. // 下划线模式下,横线的样式
  146. lineStyle() {
  147. const style = {}
  148. style.height = this.hairline ? '2px' : '4px'
  149. style.width = addUnit(this.size)
  150. // 线条模式下,背景色即为边框颜色
  151. style.backgroundColor = this.borderColor
  152. return style
  153. }
  154. },
  155. emits: ["change", 'finish', "update:modelValue"],
  156. methods: {
  157. addUnit,
  158. // 监听输入框的值发生变化
  159. inputHandler(e) {
  160. const value = e.detail.value
  161. this.inputValue = value
  162. // 是否允许输入“.”符号
  163. if(this.disabledDot) {
  164. this.$nextTick(() => {
  165. this.inputValue = value.replace('.', '')
  166. })
  167. }
  168. // 未达到maxlength之前,发送change事件,达到后发送finish事件
  169. this.$emit('change', value)
  170. // 修改通过v-model双向绑定的值
  171. // #ifdef VUE3
  172. this.$emit("update:modelValue", value);
  173. // #endif
  174. // #ifdef VUE2
  175. this.$emit("input", value);
  176. // #endif
  177. // 达到用户指定输入长度时,发出完成事件
  178. if (String(value).length >= Number(this.maxlength)) {
  179. this.$emit('finish', value)
  180. }
  181. }
  182. }
  183. }
  184. </script>
  185. <style lang="scss" scoped>
  186. @import "../../libs/css/components.scss";
  187. $u-code-input-cursor-width: 1px;
  188. $u-code-input-cursor-height: 40%;
  189. $u-code-input-cursor-animation-duration: 1s;
  190. $u-code-input-cursor-animation-name: u-cursor-flicker;
  191. .u-code-input {
  192. @include flex;
  193. position: relative;
  194. overflow: hidden;
  195. &__item {
  196. @include flex;
  197. justify-content: center;
  198. align-items: center;
  199. position: relative;
  200. &__text {
  201. font-size: 15px;
  202. color: $u-content-color;
  203. }
  204. &__dot {
  205. width: 7px;
  206. height: 7px;
  207. border-radius: 100px;
  208. background-color: $u-content-color;
  209. }
  210. &__line {
  211. position: absolute;
  212. bottom: 0;
  213. height: 4px;
  214. border-radius: 100px;
  215. width: 40px;
  216. background-color: $u-content-color;
  217. }
  218. /* #ifndef APP-PLUS */
  219. &__cursor {
  220. position: absolute;
  221. top: 50%;
  222. left: 50%;
  223. transform: translate(-50%,-50%);
  224. width: $u-code-input-cursor-width;
  225. height: $u-code-input-cursor-height;
  226. animation: $u-code-input-cursor-animation-duration u-cursor-flicker infinite;
  227. }
  228. /* #endif */
  229. }
  230. &__input {
  231. // 之所以需要input输入框,是因为有它才能唤起键盘
  232. // 这里将它设置为两倍的屏幕宽度,再将左边的一半移出屏幕,为了不让用户看到输入的内容
  233. position: absolute;
  234. left: -750rpx;
  235. width: 1500rpx;
  236. top: 0;
  237. background-color: transparent;
  238. text-align: left;
  239. }
  240. }
  241. /* #ifndef APP-PLUS */
  242. @keyframes u-cursor-flicker {
  243. 0% {
  244. opacity: 0;
  245. }
  246. 50% {
  247. opacity: 1;
  248. }
  249. 100% {
  250. opacity: 0;
  251. }
  252. }
  253. /* #endif */
  254. </style>