tui-code-input.vue 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. <template>
  2. <view class="tui-code__input" :style="{marginTop:marginTop+'rpx',marginBottom:marginBottom+'rpx'}" @tap="onClick">
  3. <view class="tui-code__input" :style="{paddingLeft:gap+'rpx',paddingRight:gap+'rpx'}">
  4. <view class="tui-cinput__item"
  5. :style="{width:width+'rpx',height:height+'rpx',background:background,borderRadius:radius+'rpx',borderColor:activeIndex===index || inputVal[index]?activeColor:borderColor,borderTopWidth:(borderType==1?borderWidth:0)+'rpx',borderLeftWidth:(borderType==1?borderWidth:0)+'rpx',borderRightWidth:(borderType==1?borderWidth:0)+'rpx',borderBottomWidth:(borderType==1 || borderType==2?borderWidth:0)+'rpx'}"
  6. @tap="onTap" v-for="(item,index) in inputArr" :key="index">
  7. <text class="tui-cinput__text"
  8. :style="{width:width+'rpx',height:height+'rpx',fontSize:size+'rpx',lineHeight:height+'rpx',color:color,fontWeight:fontWeight}">{{password?(inputVal[index] ? '●':''):(inputVal[index] || '')}}</text>
  9. <text class="tui-cinput__placeholder"
  10. :style="{fontSize:size+'rpx',fontWeight:fontWeight}">{{password?(inputVal[index] ? '●':''):(inputVal[index] || '')}}</text>
  11. <view class="tui-cinput__cursor" :class="{'tui-cinput__cursor-ani':activeIndex===index && focus}"
  12. v-if="cursor && !disabled" :style="{height:cursorHeight+'rpx',background:cursorColor}">
  13. </view>
  14. </view>
  15. </view>
  16. <input :value="val" :password="password" :type="type" class="tui-cinput__hidden"
  17. :class="{'tui-cinput__ali':ali}" @input="onInput" @blur="onBlur" :focus="focus" :maxlength="length"
  18. :disabled="disabled" @confirm="onConfirm" @focus="onTap" />
  19. </view>
  20. </template>
  21. <script>
  22. export default {
  23. name: "tuiCodeInput",
  24. emits: ['complete', 'focus', 'input', 'blur', 'confirm'],
  25. props: {
  26. //组件外层左右间距
  27. gap: {
  28. type: [Number, String],
  29. default: 80
  30. },
  31. marginTop: {
  32. type: [Number, String],
  33. default: 0
  34. },
  35. marginBottom: {
  36. type: [Number, String],
  37. default: 0
  38. },
  39. //初始值,不可超过length长度
  40. value: {
  41. type: String,
  42. default: ''
  43. },
  44. //H5不支持动态切换type类型
  45. type: {
  46. type: String,
  47. default: 'text'
  48. },
  49. password: {
  50. type: Boolean,
  51. default: false
  52. },
  53. disabled: {
  54. type: Boolean,
  55. default: false
  56. },
  57. //获取焦点
  58. isFocus: {
  59. type: Boolean,
  60. default: false
  61. },
  62. cursor: {
  63. type: Boolean,
  64. default: true
  65. },
  66. cursorColor: {
  67. type: String,
  68. default: '#5677fc'
  69. },
  70. cursorHeight: {
  71. type: [Number, String],
  72. default: 60
  73. },
  74. //输入框个数
  75. length: {
  76. type: Number,
  77. default: 4
  78. },
  79. width: {
  80. type: [Number, String],
  81. default: 108
  82. },
  83. height: {
  84. type: [Number, String],
  85. default: 108
  86. },
  87. background: {
  88. type: String,
  89. default: 'transparent'
  90. },
  91. //1-显示所有边框 2-只显示底部边框,3-无边框
  92. borderType: {
  93. type: [Number, String],
  94. default: 1
  95. },
  96. borderColor: {
  97. type: String,
  98. default: '#eaeef1'
  99. },
  100. activeColor: {
  101. type: String,
  102. default: '#5677fc'
  103. },
  104. borderWidth: {
  105. type: [Number, String],
  106. default: 2
  107. },
  108. radius: {
  109. type: [Number, String],
  110. default: 0
  111. },
  112. size: {
  113. type: [Number, String],
  114. default: 48
  115. },
  116. color: {
  117. type: String,
  118. default: '#333'
  119. },
  120. fontWeight: {
  121. type: [Number, String],
  122. default: 600
  123. }
  124. },
  125. data() {
  126. return {
  127. inputArr: [],
  128. inputVal: [],
  129. focus: false,
  130. activeIndex: -1,
  131. ali: false,
  132. val: ''
  133. };
  134. },
  135. watch: {
  136. length(val) {
  137. const nums = Number(val);
  138. if (nums !== this.inputArr.length) {
  139. this.inputArr = this.getArr(nums)
  140. }
  141. },
  142. value(val) {
  143. this.focus = true;
  144. val = val.replace(/\s+/g, "")
  145. this.getVals(val)
  146. },
  147. isFocus(val) {
  148. this.initFocus(val)
  149. }
  150. },
  151. created() {
  152. this.inputArr = this.getArr(Number(this.length))
  153. let val = this.value.replace(/\s+/g, "")
  154. this.getVals(val, true)
  155. },
  156. mounted() {
  157. setTimeout(() => {
  158. this.initFocus(this.isFocus)
  159. }, 300)
  160. },
  161. methods: {
  162. initFocus(val) {
  163. if (this.disabled) return;
  164. if (val && this.activeIndex === -1) {
  165. this.activeIndex = 0
  166. }
  167. if (!this.value && !val) {
  168. this.activeIndex = -1
  169. }
  170. this.$nextTick(() => {
  171. this.focus = val
  172. })
  173. },
  174. getArr(end) {
  175. return Array.from(new Array(end + 1).keys()).slice(1);
  176. },
  177. getVals(val, init = false) {
  178. this.val = val
  179. if (!val) {
  180. this.inputVal = []
  181. this.activeIndex = init ? -1 : 0;
  182. } else {
  183. let vals = val.split('')
  184. let arr = []
  185. this.inputArr.forEach((item, index) => {
  186. arr.push(vals[index] || '')
  187. })
  188. this.inputVal = arr
  189. const len = vals.length;
  190. this.activeIndex = len > this.length ? this.length : len;
  191. if (len === this.length) {
  192. this.$emit('complete', {
  193. detail: {
  194. value: val
  195. }
  196. })
  197. this.focus = false;
  198. uni.hideKeyboard()
  199. }
  200. }
  201. },
  202. onTap() {
  203. if (this.disabled) return;
  204. this.focus = true;
  205. if (this.activeIndex === -1) {
  206. this.activeIndex = 0
  207. }
  208. if (this.activeIndex === this.length) {
  209. this.activeIndex--;
  210. }
  211. this.$emit('focus', {})
  212. },
  213. onInput(e) {
  214. let value = e.detail.value;
  215. value = value.replace(/\s+/g, "")
  216. this.getVals(value)
  217. this.$emit('input', {
  218. detail: {
  219. value: value
  220. }
  221. })
  222. },
  223. onBlur(e) {
  224. let value = e.detail.value;
  225. value = value.replace(/\s+/g, "")
  226. this.focus = false
  227. // #ifdef MP-ALIPAY
  228. this.ali = false
  229. // #endif
  230. if (!value) {
  231. this.activeIndex = -1;
  232. }
  233. this.$emit('blur', {
  234. detail: {
  235. value: value
  236. }
  237. })
  238. },
  239. onConfirm(e) {
  240. this.focus = false;
  241. uni.hideKeyboard()
  242. this.$emit('confirm', e)
  243. },
  244. onClick() {
  245. // #ifdef MP-ALIPAY
  246. setTimeout(() => {
  247. this.ali = true
  248. }, 50)
  249. // #endif
  250. },
  251. clear() {
  252. this.val = ''
  253. this.inputVal = []
  254. this.activeIndex = -1;
  255. this.$nextTick(() => {
  256. this.onTap()
  257. })
  258. }
  259. }
  260. }
  261. </script>
  262. <style scoped>
  263. .tui-code__input {
  264. position: relative;
  265. /* #ifdef MP-BAIDU */
  266. max-width: 100%;
  267. overflow: hidden;
  268. /* #endif */
  269. }
  270. .tui-code__input {
  271. /* #ifndef APP-NVUE */
  272. width: 100%;
  273. display: flex;
  274. box-sizing: border-box;
  275. /* #endif */
  276. flex: 1;
  277. flex-direction: row;
  278. align-items: center;
  279. justify-content: space-between;
  280. }
  281. .tui-cinput__item {
  282. /* #ifndef APP-NVUE */
  283. display: flex;
  284. box-sizing: border-box;
  285. /* #endif */
  286. flex-direction: row;
  287. justify-content: center;
  288. align-items: center;
  289. border-style: solid;
  290. position: relative;
  291. overflow: hidden;
  292. }
  293. .tui-cinput__text {
  294. position: absolute;
  295. left: 0;
  296. top: 0;
  297. flex: 1;
  298. display: flex;
  299. flex-direction: row;
  300. align-items: center;
  301. justify-content: center;
  302. text-align: center;
  303. }
  304. .tui-cinput__placeholder {
  305. text-align: center;
  306. opacity: 0;
  307. }
  308. .tui-cinput__cursor {
  309. border-radius: 2px;
  310. width: 0;
  311. }
  312. .tui-cinput__cursor-ani {
  313. width: 2px;
  314. animation: ani_cursor 1s infinite steps(1, start);
  315. }
  316. @keyframes ani_cursor {
  317. 0% {
  318. opacity: 0;
  319. }
  320. 50% {
  321. opacity: 1;
  322. }
  323. 100% {
  324. opacity: 0;
  325. }
  326. }
  327. .tui-cinput__hidden {
  328. position: absolute;
  329. left: 0;
  330. top: 0;
  331. /* #ifndef MP */
  332. right: 0;
  333. bottom: 0;
  334. /* #endif */
  335. /* #ifndef MP-WEIXIN || MP-QQ */
  336. width: 100%;
  337. height: 100%;
  338. /* #endif */
  339. z-index: 2;
  340. /* #ifdef MP-WEIXIN || MP-QQ || MP-TOUTIAO */
  341. height: 0;
  342. width: 0;
  343. border: none;
  344. /* #endif */
  345. margin: 0;
  346. padding: 0;
  347. opacity: 0;
  348. /* #ifdef MP-ALIPAY || MP-BAIDU || MP-TOUTIAO */
  349. font-size: 0;
  350. /* #endif */
  351. /* #ifdef MP-BAIDU */
  352. transform: scaleX(2);
  353. transform-origin: 100% center;
  354. /* #endif */
  355. color: transparent;
  356. }
  357. /* #ifdef MP-ALIPAY */
  358. .tui-cinput__ali {
  359. height: 0;
  360. width: 0;
  361. }
  362. /* #endif */
  363. </style>