index.jsx 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. import React, { useEffect, useState, useRef } from 'react';
  2. import styles from './styles.module.scss';
  3. import { APP_OPT_LENGTH } from '@constants/app';
  4. const MobileCodeInput = ({ length = APP_OPT_LENGTH, onChange, className = '', onFocus, onBlur }) => {
  5. const inputLength = Number.isInteger(length) && length > 0 ? length : APP_OPT_LENGTH;
  6. // 初始化一个长度为6的空字符串数组
  7. const [code, setCode] = useState(new Array(inputLength).fill(''));
  8. const [active, setActive] = useState(false);
  9. // 用于存储每个输入框的DOM引用,方便控制焦点
  10. const inputRefs = useRef([]);
  11. useEffect(() => {
  12. setCode(new Array(inputLength).fill(''));
  13. setActive(false);
  14. inputRefs.current = inputRefs.current.slice(0, inputLength);
  15. }, [inputLength]);
  16. // 新增:处理聚焦
  17. const handleFocus = (index) => {
  18. onFocus?.(index); // 传回聚焦的输入框索引
  19. };
  20. // 新增:处理失焦
  21. const handleBlur = () => {
  22. onBlur?.();
  23. };
  24. const handleCodeChanged = (newCode) => {
  25. const completed = newCode.every((char) => char !== '') && newCode.length === inputLength;
  26. setActive(completed);
  27. onChange?.(newCode.join(''));
  28. }
  29. // 处理输入变化
  30. const handleChange = (index, e) => {
  31. const value = e.target.value;
  32. // 仅允许输入数字
  33. if (!/^\d*$/.test(value)) return;
  34. const newCode = [...code];
  35. // 如果用户快速输入覆盖了原数字,取最后一位
  36. newCode[index] = value.substring(value.length - 1);
  37. setCode(newCode);
  38. // 如果当前输入框有值,且不是最后一个,则焦点自动移向下一个
  39. if (value && index < inputLength - 1) {
  40. inputRefs.current[index + 1]?.focus();
  41. }
  42. // 检查是否所有格子都已填满,如果是则触发回调
  43. handleCodeChanged(newCode);
  44. };
  45. // 处理键盘按键事件(主要处理 Backspace 逻辑)
  46. const handleKeyDown = (index, e) => {
  47. // 当按下退格键,且当前格子为空时,焦点自动移向上一个格子
  48. if (e.key === 'Backspace' && !code[index] && index > 0) {
  49. inputRefs.current[index - 1]?.focus();
  50. }
  51. };
  52. // 处理粘贴事件
  53. const handlePaste = (e) => {
  54. e.preventDefault();
  55. // 获取粘贴板中的文本
  56. const pastedData = e.clipboardData.getData('text/plain').trim();
  57. // 过滤非数字字符,并截取到指定长度
  58. const cleanData = pastedData.replace(/\D/g, '').slice(0, inputLength);
  59. if (!cleanData) return;
  60. const newCode = new Array(inputLength).fill('');
  61. const pastedArray = cleanData.split('');
  62. // 将粘贴的数据分配到数组中
  63. pastedArray.forEach((char, i) => {
  64. newCode[i] = char;
  65. });
  66. setCode(newCode);
  67. // 计算粘贴后焦点应该停留在哪个输入框
  68. const focusIndex = pastedArray.length < inputLength ? pastedArray.length : inputLength - 1;
  69. inputRefs.current[focusIndex]?.focus();
  70. // 如果粘贴的长度刚好等于所需长度,触发回调
  71. handleCodeChanged(newCode)
  72. };
  73. return (
  74. <div className={`${styles.container} ${className}`}>
  75. {
  76. code.map((value, index) => (
  77. <input
  78. key={index}
  79. // 将每个 input 的引用保存到 inputRefs 数组中
  80. ref={(el) => (inputRefs.current[index] = el)}
  81. type="text"
  82. inputMode="numeric" // 在移动端唤起数字键盘
  83. value={value}
  84. onChange={(e) => handleChange(index, e)}
  85. onKeyDown={(e) => handleKeyDown(index, e)}
  86. onPaste={handlePaste}
  87. className={`${styles.input_box} ${active ? styles.active : ''}`}
  88. onFocus={() => handleFocus(index)} // 新增
  89. onBlur={handleBlur} // 新增
  90. />
  91. ))}
  92. </div>
  93. );
  94. };
  95. export default MobileCodeInput;