/** * 验证码输入组件 * 参考设计:4-6位数字验证码,每个数字独立显示在一个方框中 * 支持自动聚焦、粘贴、删除回退 */ import React, { useRef, useEffect, useCallback } from 'react'; import { View, TextInput, StyleSheet, Keyboard, Platform, Text, ViewStyle, } from 'react-native'; import { useAppColors, type AppColors } from '../../theme'; interface VerificationCodeInputProps { value: string; onChangeValue: (value: string) => void; length?: number; // 验证码位数,默认4位 autoFocus?: boolean; onComplete?: (value: string) => void; // 输入完成回调 containerStyle?: ViewStyle; disabled?: boolean; } const CELL_WIDTH = 56; const CELL_HEIGHT = 64; const CELL_MARGIN = 8; export const VerificationCodeInput: React.FC = ({ value, onChangeValue, length = 4, autoFocus = true, onComplete, containerStyle, disabled = false, }) => { const colors = useAppColors(); const styles = createStyles(colors); // 为每个数字创建一个 ref const inputRefs = useRef<(TextInput | null)[]>(Array(length).fill(null)); // 将 value 分割为单个字符数组 const chars = value.split('').slice(0, length); // 填充空位 while (chars.length < length) { chars.push(''); } // 获取当前聚焦的位置 const getFocusedIndex = useCallback(() => { return Math.min(value.length, length - 1); }, [value.length, length]); // 聚焦到指定位置 const focusInput = useCallback((index: number) => { if (index >= 0 && index < length) { inputRefs.current[index]?.focus(); } }, [length]); // 自动聚焦第一个输入框 useEffect(() => { if (autoFocus && !disabled) { const timer = setTimeout(() => { focusInput(0); }, 100); return () => clearTimeout(timer); } }, [autoFocus, disabled, focusInput]); // 当值改变时,自动聚焦到下一个输入框 useEffect(() => { if (value.length < length) { focusInput(value.length); } // 当输入完成时触发回调 if (value.length === length && onComplete) { onComplete(value); } }, [value, length, onComplete, focusInput]); // 处理文本变化 const handleChangeText = (text: string, index: number) => { if (disabled) return; // 只允许数字 const numericText = text.replace(/[^0-9]/g, ''); if (numericText.length === 0) { // 删除操作 if (value.length > index) { // 删除当前位置的字符 const newValue = value.slice(0, index) + value.slice(index + 1); onChangeValue(newValue); // 聚焦到前一个输入框 if (index > 0) { focusInput(index - 1); } } } else if (numericText.length === 1) { // 单个字符输入 let newValue: string; if (index < value.length) { // 替换已有字符 newValue = value.slice(0, index) + numericText + value.slice(index + 1); } else { // 追加新字符 newValue = value + numericText; } onChangeValue(newValue.slice(0, length)); // 自动聚焦到下一个 if (index < length - 1) { focusInput(index + 1); } } else if (numericText.length > 1) { // 粘贴操作:尝试将粘贴的内容作为完整验证码 const newValue = numericText.slice(0, length); onChangeValue(newValue); // 聚焦到最后一个或下一个 const focusIndex = Math.min(newValue.length, length - 1); focusInput(focusIndex); } }; // 处理按键事件(用于删除键) const handleKeyPress = (e: any, index: number) => { if (disabled) return; if (e.nativeEvent.key === 'Backspace') { if (value.length > index) { // 删除当前位置的字符 const newValue = value.slice(0, index) + value.slice(index + 1); onChangeValue(newValue); } else if (index > 0 && value.length === index) { // 当前位置没有字符,删除前一个 const newValue = value.slice(0, -1); onChangeValue(newValue); focusInput(index - 1); } } }; // 聚焦时全选文本 const handleFocus = (index: number) => { if (disabled) return; inputRefs.current[index]?.setNativeProps({ selection: { start: 0, end: 1 }, }); }; return ( {chars.map((char, index) => { const isFocused = value.length === index; const isFilled = char !== ''; const isLastFilled = index === value.length - 1 && isFilled; return ( { inputRefs.current[index] = ref; }} style={styles.cellInput} value={char} onChangeText={(text) => handleChangeText(text, index)} onKeyPress={(e) => handleKeyPress(e, index)} onFocus={() => handleFocus(index)} keyboardType="number-pad" maxLength={1} selectTextOnFocus editable={!disabled} caretHidden={true} textContentType="oneTimeCode" // iOS 自动填充支持 /> {/* 光标指示器 */} {isFocused && !isFilled && !disabled && ( )} ); })} ); }; function createStyles(colors: AppColors) { return StyleSheet.create({ container: { alignItems: 'center', justifyContent: 'center', }, cellsContainer: { flexDirection: 'row', justifyContent: 'center', alignItems: 'center', }, cell: { width: CELL_WIDTH, height: CELL_HEIGHT, borderRadius: 12, borderWidth: 1.5, borderColor: colors.divider, backgroundColor: colors.background.paper, marginHorizontal: CELL_MARGIN / 2, justifyContent: 'center', alignItems: 'center', position: 'relative', }, cellFocused: { borderColor: colors.primary.main, backgroundColor: `${colors.primary.main}10`, }, cellFilled: { borderColor: colors.primary.main, }, cellDisabled: { opacity: 0.5, backgroundColor: colors.background.disabled, }, cellInput: { width: '100%', height: '100%', textAlign: 'center', fontSize: 28, fontWeight: '600', color: colors.text.primary, padding: 0, }, cursor: { position: 'absolute', width: 2, height: 28, backgroundColor: colors.primary.main, borderRadius: 1, }, }); } export default VerificationCodeInput;