tn-verification-code-input.vue 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. <template>
  2. <view class="tn-verification-code-class tn-verification-code">
  3. <view class="tn-code__container">
  4. <input class="tn-code__input" :disabled="disabledKeyboard" :value="valueModel" type="number" :focus="focus" :maxlength="maxLength" @input="getValue" />
  5. <view v-for="(item, index) in loopCharArr" :key="index">
  6. <view
  7. class="tn-code__item"
  8. :class="[{
  9. 'tn-code__item--breathe': breathe && charArrLength === index,
  10. 'tn-code__item__box': mode === 'box',
  11. 'tn-code__item__box--active': mode === 'box' && charArrLength === index
  12. }]"
  13. :style="[itemStyle(index)]"
  14. >
  15. <view
  16. v-if="mode !== 'middleLine'"
  17. class="tn-code__item__line tn-code__item__line--placeholder"
  18. :style="[placeholderLineStyle(index)]"
  19. ></view>
  20. <view
  21. v-if="mode === 'middleLine' && charArrLength <= index"
  22. class="tn-code__item__line tn-code__item__line--middle"
  23. :class="[{
  24. 'tn-code__item__line--bold': bold,
  25. 'tn-code__item--breathe': breathe && charArrLength === index,
  26. 'tn-code__item__line--active': charArrLength === index
  27. }]"
  28. :style="[lineStyle(index)]"
  29. ></view>
  30. <view
  31. v-if="mode === 'bottomLine'"
  32. class="tn-code__item__line tn-code__item__line--bottom"
  33. :class="[{
  34. 'tn-code__item__line--bold': bold,
  35. 'tn-code__item--breathe': breathe && charArrLength === index,
  36. 'tn-code__item__line--active': charArrLength === index
  37. }]"
  38. :style="[lineStyle(index)]"
  39. ></view>
  40. <block v-if="!dotFill">
  41. <text>{{ charArr[index] ? charArr[index] : '' }}</text>
  42. </block>
  43. <block v-else>
  44. <text class="tn-code__item__dot">{{ charArr[index] ? '●' : '' }}</text>
  45. </block>
  46. </view>
  47. </view>
  48. </view>
  49. </view>
  50. </template>
  51. <script>
  52. export default {
  53. name: 'tn-verification-code',
  54. props: {
  55. // 验证码的值
  56. value: {
  57. type: [String, Number],
  58. default: ''
  59. },
  60. // 最大输入长度
  61. maxLength: {
  62. type: Number,
  63. default: 4
  64. },
  65. // 显示模式
  66. // box -> 盒子 bottomLine -> 底部横线 middleLine -> 中间横线
  67. mode: {
  68. type: String,
  69. default: 'box'
  70. },
  71. // 用圆点填充空白位置
  72. dotFill: {
  73. type: Boolean,
  74. default: false
  75. },
  76. // 字体加粗
  77. bold: {
  78. type: Boolean,
  79. default: false
  80. },
  81. // 字体大小
  82. fontSize: {
  83. type: [String, Number],
  84. default: ''
  85. },
  86. // 激活时颜色
  87. activeColor: {
  88. type: String,
  89. default: ''
  90. },
  91. // 未激活时颜色
  92. inactiveColor: {
  93. type: String,
  94. default: ''
  95. },
  96. // 输入框宽度,单位rpx
  97. inputWidth: {
  98. type: Number,
  99. default: 80
  100. },
  101. // 当前激活的item带呼吸效果
  102. breathe: {
  103. type: Boolean,
  104. default: true
  105. },
  106. // 自动获取焦点
  107. focus: {
  108. type: Boolean,
  109. default: false
  110. },
  111. // 隐藏原生键盘,当使用自定义键盘的时候设置该参数未true即可
  112. disabledKeyboard: {
  113. type: Boolean,
  114. default: false
  115. }
  116. },
  117. computed: {
  118. // 拆分要显示的字符
  119. charArr() {
  120. return this.valueModel.split('')
  121. },
  122. // 当前输入字符的长度
  123. charArrLength() {
  124. return this.charArr.length
  125. },
  126. // 输入框的个数
  127. loopCharArr() {
  128. return new Array(this.maxLength)
  129. },
  130. itemStyle() {
  131. return (index) => {
  132. let style = {}
  133. style.fontWeight = this.bold ? 'bold' : 'normal'
  134. if (this.fontSize) {
  135. style.fontSize = this.fontSize + 'rpx'
  136. }
  137. if (this.inputWidth) {
  138. style.width = this.inputWidth + 'rpx'
  139. style.height = this.inputWidth + 'rpx'
  140. style.lineHeight = this.inputWidth + 'rpx'
  141. }
  142. if (this.inactiveColor) {
  143. style.color = this.inactiveColor
  144. style.borderColor = this.inactiveColor
  145. }
  146. if (this.mode === 'box' && this.charArrLength === index) {
  147. style.borderColor = this.activeColor
  148. }
  149. return style
  150. }
  151. },
  152. placeholderLineStyle() {
  153. return (index) => {
  154. let style = {}
  155. style.display = this.charArrLength === index ? 'block' : 'none'
  156. if (this.inputWidth) {
  157. style.height = (this.inputWidth * 0.5) + 'rpx'
  158. }
  159. return style
  160. }
  161. },
  162. lineStyle() {
  163. return (index) => {
  164. let style = {}
  165. if (this.inactiveColor) {
  166. style.backgroundColor = this.inactiveColor
  167. }
  168. if (this.charArrLength === index && this.activeColor) {
  169. style.backgroundColor = this.activeColor
  170. }
  171. return style
  172. }
  173. }
  174. },
  175. watch: {
  176. value: {
  177. handler(val) {
  178. // 转换为字符串
  179. val = String(val)
  180. // 截掉超出的部分
  181. this.valueModel = val.substring(0, this.maxLength)
  182. },
  183. immediate: true
  184. }
  185. },
  186. data() {
  187. return {
  188. valueModel: ''
  189. }
  190. },
  191. methods: {
  192. // 获取填写的值
  193. getValue(e) {
  194. const {
  195. value
  196. } = e.detail
  197. this.valueModel = value
  198. // 判断输入的长度是否超出了maxlength的值
  199. if (String(value).length > this.maxLength) return
  200. // 未达到maxlength之前,触发change事件,否则触发finish事件
  201. this.$emit('change', value)
  202. this.$emit('input', value)
  203. if (String(value).length == this.maxLength) {
  204. this.$emit('finish', value)
  205. }
  206. }
  207. }
  208. }
  209. </script>
  210. <style lang="scss" scoped>
  211. .tn-verification-code {
  212. text-align: center;
  213. .tn-code {
  214. &__container {
  215. display: flex;
  216. flex-direction: row;
  217. justify-content: center;
  218. flex-wrap: wrap;
  219. position: relative;
  220. }
  221. &__input {
  222. position: absolute;
  223. top: 0;
  224. left: -100%;
  225. width: 200%;
  226. height: 100%;
  227. text-align: left;
  228. z-index: 9;
  229. opacity: 0;
  230. background: none;
  231. }
  232. &__item {
  233. position: relative;
  234. width: 80rpx;
  235. height: 80rpx;
  236. line-height: 80rpx;
  237. display: flex;
  238. flex-direction: row;
  239. align-items: center;
  240. justify-content: center;
  241. margin: 10rpx 10rpx;
  242. font-size: 60rpx;
  243. font-weight: bold;
  244. color: #838383;
  245. &--breathe {
  246. animation: breathe 2s infinite ease;
  247. }
  248. &__box {
  249. border: 2rpx solid #AAAAAA;
  250. border-radius: 6rpx;
  251. &--active {
  252. animation-timing-function: ease-in-out;
  253. animation-duration: 1500ms;
  254. animation-iteration-count: infinite;
  255. animation-direction: alternate;
  256. overflow: hidden;
  257. border: 2rpx solid #01BEFF;
  258. }
  259. }
  260. &__line {
  261. position: absolute;
  262. top: 50%;
  263. left: 50%;
  264. transform: translate(-50%, -50%);
  265. background-color: #AAAAAA;
  266. &--bold {
  267. height: 4px !important;
  268. }
  269. &--placeholder {
  270. display: none;
  271. width: 2rpx;
  272. height: 40rpx;
  273. }
  274. &--middle, &--bottom {
  275. width: 80%;
  276. height: 2px;
  277. border-radius: 2px;
  278. }
  279. &--bottom {
  280. top: auto !important;
  281. bottom: 0;
  282. transform: translateX(-50%) !important;
  283. }
  284. &--active {
  285. background-color: #01BEFF !important;
  286. }
  287. }
  288. &__dot {
  289. font-size: 34rpx;
  290. line-height: 34rpx;
  291. }
  292. }
  293. }
  294. }
  295. @keyframes breathe {
  296. 0% {
  297. opacity: 0.3;
  298. }
  299. 50% {
  300. opacity: 1;
  301. }
  302. 100% {
  303. opacity: 0.3;
  304. }
  305. }
  306. </style>