verify-code.vue 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. <template>
  2. <view class="xt__verify-code">
  3. <!-- 输入框 -->
  4. <input
  5. id="xt__input"
  6. :value="code"
  7. class="xt__input"
  8. :focus="isFocus"
  9. :password="isPassword"
  10. :type="inputType"
  11. :maxlength="size"
  12. @input="input"
  13. @focus="inputFocus"
  14. @blur="inputBlur"
  15. />
  16. <!-- 光标 -->
  17. <view
  18. id="xt__cursor"
  19. v-if="cursorVisible && type !== 'middle'"
  20. class="xt__cursor"
  21. :style="{ left: codeCursorLeft[code.length] + 'px', height: cursorHeight + 'px', backgroundColor: cursorColor }"
  22. ></view>
  23. <!-- 输入框 - 组 -->
  24. <view id="xt__input-ground" class="xt__input-ground">
  25. <template v-for="(item, index) in size">
  26. <view
  27. :key="index"
  28. :style="{ borderColor: code.length === index && cursorVisible ? boxActiveColor : boxNormalColor }"
  29. :class="['xt__box', `xt__box-${type + ''}`, `xt__box::after`]"
  30. >
  31. <view :style="{ borderColor: boxActiveColor }" class="xt__middle-line" v-if="type === 'middle' && !code[index]"></view>
  32. <text class="xt__code-text">{{ code[index] | codeFormat(isPassword) }}</text>
  33. </view>
  34. </template>
  35. </view>
  36. </view>
  37. </template>
  38. <script>
  39. /**
  40. * @description 输入验证码组件
  41. * @property {string} type = [box|middle|bottom] - 显示类型 默认:box -eg:bottom
  42. * @property {string} inputType = [text|number] - 输入框类型 默认:number -eg:number
  43. * @property {number} size = [4|6] - 支持的验证码数量 默认:6 -eg:6
  44. * @property {boolean} isFocus - 是否立即聚焦 默认:true
  45. * @property {boolean} isPassword - 是否以密码形式显示 默认false -eg:false
  46. * @property {string} cursorColor - 光标颜色 默认:#cccccc
  47. * @property {string} boxNormalColor - 光标未聚焦到的框的颜色 默认:#cccccc
  48. * @property {string} boxActiveColor - 光标聚焦到的框的颜色 默认:#000000
  49. * @event {Function(data)} confirm - 输入完成
  50. */
  51. export default {
  52. name: 'xt-verify-code',
  53. props: {
  54. value: {
  55. type: String,
  56. default: () => ''
  57. },
  58. type: {
  59. type: String,
  60. default: () => 'box'
  61. },
  62. inputType: {
  63. type: String,
  64. default: () => 'number'
  65. },
  66. size: {
  67. type: Number,
  68. default: () => 6
  69. },
  70. isFocus: {
  71. type: Boolean,
  72. default: () => true
  73. },
  74. isPassword: {
  75. type: Boolean,
  76. default: () => false
  77. },
  78. cursorColor: {
  79. type: String,
  80. default: () => '#cccccc'
  81. },
  82. boxNormalColor: {
  83. type: String,
  84. default: () => '#cccccc'
  85. },
  86. boxActiveColor: {
  87. type: String,
  88. default: () => '#000000'
  89. }
  90. },
  91. model: {
  92. prop: 'value',
  93. event: 'input'
  94. },
  95. data() {
  96. return {
  97. cursorVisible: false,
  98. cursorHeight: 35,
  99. code: '', // 输入的验证码
  100. codeCursorLeft: [] // 向左移动的距离数组
  101. };
  102. },
  103. created() {
  104. this.cursorVisible = this.isFocus;
  105. },
  106. mounted() {
  107. this.init();
  108. },
  109. methods: {
  110. /**
  111. * @description 初始化
  112. */
  113. init() {
  114. this.getCodeCursorLeft();
  115. this.setCursorHeight();
  116. },
  117. /**
  118. * @description 获取元素节点
  119. * @param {string} elm - 节点的id、class 相当于 document.querySelect的参数 -eg: #id
  120. * @param {string} type = [single|array] - 单个元素获取多个元素 默认是单个元素
  121. * @param {Function} callback - 回调函数
  122. */
  123. getElement(elm, type = 'single', callback) {
  124. uni
  125. .createSelectorQuery()
  126. .in(this)
  127. [type === 'array' ? 'selectAll' : 'select'](elm)
  128. .boundingClientRect()
  129. .exec(data => {
  130. callback(data[0]);
  131. });
  132. },
  133. /**
  134. * @description 计算光标的高度
  135. */
  136. setCursorHeight() {
  137. this.getElement('.xt__box', 'single', boxElm => {
  138. this.cursorHeight = boxElm.height * 0.6;
  139. });
  140. },
  141. /**
  142. * @description 获取光标在每一个box的left位置
  143. */
  144. getCodeCursorLeft() {
  145. // 获取父级框的位置信息
  146. this.getElement('#xt__input-ground', 'single', parentElm => {
  147. const parentLeft = parentElm.left;
  148. // 获取各个box信息
  149. this.getElement('.xt__box', 'array', elms => {
  150. this.codeCursorLeft = [];
  151. elms.forEach(elm => {
  152. this.codeCursorLeft.push(elm.left - parentLeft + elm.width / 2);
  153. });
  154. });
  155. });
  156. },
  157. // 输入框输入变化的回调
  158. input(e) {
  159. const value = e.detail.value;
  160. this.cursorVisible = value.length !== this.size;
  161. this.$emit('input', value);
  162. this.inputSuccess(value);
  163. },
  164. // 输入完成回调
  165. inputSuccess(value) {
  166. if (value.length === this.size) {
  167. this.$emit('confirm', value);
  168. }
  169. },
  170. // 输入聚焦
  171. inputFocus() {
  172. this.cursorVisible = this.code.length !== this.size;
  173. },
  174. // 输入失去焦点
  175. inputBlur() {
  176. this.cursorVisible = false;
  177. }
  178. },
  179. watch: {
  180. value(val) {
  181. this.code = val;
  182. }
  183. },
  184. filters: {
  185. codeFormat(val, isPassword) {
  186. let value = '';
  187. if (val) {
  188. value = isPassword ? '*' : val;
  189. }
  190. return value;
  191. }
  192. }
  193. };
  194. </script>
  195. <style lang="scss" scoped>
  196. .xt__verify-code {
  197. position: relative;
  198. width: 100%;
  199. box-sizing: border-box;
  200. .xt__input {
  201. height: 100%;
  202. width: 200%;
  203. position: absolute;
  204. left: -100%;
  205. z-index: 1;
  206. }
  207. .xt__cursor {
  208. position: absolute;
  209. top: 50%;
  210. transform: translateY(-50%);
  211. display: inline-block;
  212. width: 2px;
  213. animation-name: cursor;
  214. animation-duration: 0.8s;
  215. animation-iteration-count: infinite;
  216. }
  217. .xt__input-ground {
  218. display: flex;
  219. justify-content: space-between;
  220. align-items: center;
  221. width: 100%;
  222. box-sizing: border-box;
  223. .xt__box {
  224. position: relative;
  225. display: inline-block;
  226. width: 76rpx;
  227. height: 112rpx;
  228. &-bottom {
  229. border-bottom-width: 2px;
  230. border-bottom-style: solid;
  231. }
  232. &-box {
  233. border-width: 2px;
  234. border-style: solid;
  235. }
  236. &-middle {
  237. border: none;
  238. }
  239. .xt__middle-line {
  240. position: absolute;
  241. top: 50%;
  242. left: 50%;
  243. width: 50%;
  244. transform: translate(-50%, -50%);
  245. border-bottom-width: 2px;
  246. border-bottom-style: solid;
  247. }
  248. .xt__code-text {
  249. position: absolute;
  250. top: 50%;
  251. left: 50%;
  252. font-size:52rpx;
  253. transform: translate(-50%, -50%);
  254. }
  255. }
  256. }
  257. }
  258. @keyframes cursor {
  259. 0% {
  260. opacity: 1;
  261. }
  262. 100% {
  263. opacity: 0;
  264. }
  265. }
  266. </style>