You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django+React注册:特定验证错误无法在UI展示的解决方案

Django + React 注册功能字段级错误精准显示修复方案

一、后端(Django DRF + Simple JWT)修改

1. 修正用户序列化器(serializers.py)

添加phone字段,规范密码验证逻辑,确保唯一性验证错误返回对应字段:

from rest_framework import serializers
from django.contrib.auth.models import User

class RegisterSerializer(serializers.ModelSerializer):
    phone = serializers.CharField(required=True, max_length=15)
    password2 = serializers.CharField(write_only=True, required=True, style={'input_type': 'password'})

    class Meta:
        model = User
        fields = ('username', 'email', 'phone', 'password', 'password2')
        extra_kwargs = {
            'password': {'write_only': True},
            'username': {
                'validators': [serializers.UniqueValidator(queryset=User.objects.all(), message="用户名已存在")]
            },
            'email': {
                'validators': [serializers.UniqueValidator(queryset=User.objects.all(), message="邮箱已存在")]
            }
        }

    def validate(self, attrs):
        if attrs['password'] != attrs['password2']:
            raise serializers.ValidationError({'password2': "两次输入的密码不匹配"})
        return attrs

    def create(self, validated_data):
        validated_data.pop('password2')
        user = User.objects.create_user(**validated_data)
        # 若User模型扩展了phone字段,可取消下方注释保存手机号
        # user.phone = validated_data['phone']
        # user.save()
        return user

2. 修正注册视图(views.py)

移除统一error字段返回,直接使用DRF序列化器自带的字段级错误响应:

from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView
from .serializers import RegisterSerializer

class RegisterView(APIView):
    def post(self, request):
        serializer = RegisterSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response({"message": "注册成功"}, status=status.HTTP_201_CREATED)
        # 返回序列化器自动生成的字段级错误字典
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

3. 确认路由配置(urls.py)

确保路由正确绑定注册视图:

from django.urls import path
from .views import RegisterView

urlpatterns = [
    path('register/', RegisterView.as_view(), name='register'),
    # 其他路由...
]

二、前端(React Vite + Daisy UI + Tailwind)修改

1. 修正AuthContext中的registerUser函数

正确捕获后端字段级错误并返回给组件:

// AuthContext.jsx
export const registerUser = async (userData) => {
    try {
        const response = await fetch('/api/register/', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify(userData),
        });

        const data = await response.json();

        if (!response.ok) {
            throw new Error(JSON.stringify(data));
        }

        return data;
    } catch (err) {
        const errorData = JSON.parse(err.message);
        return { errors: errorData };
    }
};

2. 修正Register.jsx组件

统一确认密码字段名,添加字段级错误渲染逻辑:

import { useState } from 'react';
import { registerUser } from './AuthContext';

const Register = () => {
    const [formData, setFormData] = useState({
        username: '',
        email: '',
        phone: '',
        password: '',
        password2: '',
    });
    const [errors, setErrors] = useState({});

    const handleChange = (e) => {
        setFormData(prev => ({
            ...prev,
            [e.target.name]: e.target.value
        }));
        if (errors[e.target.name]) {
            setErrors(prev => ({ ...prev, [e.target.name]: '' }));
        }
    };

    const handleSubmit = async (e) => {
        e.preventDefault();
        setErrors({});
        const result = await registerUser(formData);
        if (result.errors) {
            setErrors(result.errors);
        } else {
            alert('注册成功');
            // 注册成功后可添加跳转逻辑
        }
    };

    return (
        <div className="container mx-auto p-4">
            <form onSubmit={handleSubmit} className="space-y-4">
                {/* 用户名字段 */}
                <div>
                    <label className="label">用户名</label>
                    <input
                        type="text"
                        name="username"
                        value={formData.username}
                        onChange={handleChange}
                        className={`input input-bordered w-full ${errors.username ? 'input-error' : ''}`}
                    />
                    {errors.username && <p className="text-red-500 text-sm mt-1">{errors.username[0]}</p>}
                </div>

                {/* 邮箱字段 */}
                <div>
                    <label className="label">邮箱</label>
                    <input
                        type="email"
                        name="email"
                        value={formData.email}
                        onChange={handleChange}
                        className={`input input-bordered w-full ${errors.email ? 'input-error' : ''}`}
                    />
                    {errors.email && <p className="text-red-500 text-sm mt-1">{errors.email[0]}</p>}
                </div>

                {/* 手机号字段 */}
                <div>
                    <label className="label">手机号</label>
                    <input
                        type="text"
                        name="phone"
                        value={formData.phone}
                        onChange={handleChange}
                        className={`input input-bordered w-full ${errors.phone ? 'input-error' : ''}`}
                    />
                    {errors.phone && <p className="text-red-500 text-sm mt-1">{errors.phone[0]}</p>}
                </div>

                {/* 密码字段 */}
                <div>
                    <label className="label">密码</label>
                    <input
                        type="password"
                        name="password"
                        value={formData.password}
                        onChange={handleChange}
                        className={`input input-bordered w-full ${errors.password ? 'input-error' : ''}`}
                    />
                    {errors.password && <p className="text-red-500 text-sm mt-1">{errors.password[0]}</p>}
                </div>

                {/* 确认密码字段 */}
                <div>
                    <label className="label">确认密码</label>
                    <input
                        type="password"
                        name="password2"
                        value={formData.password2}
                        onChange={handleChange}
                        className={`input input-bordered w-full ${errors.password2 ? 'input-error' : ''}`}
                    />
                    {errors.password2 && <p className="text-red-500 text-sm mt-1">{errors.password2[0]}</p>}
                </div>

                <button type="submit" className="btn btn-primary w-full">注册</button>
            </form>
        </div>
    );
};

export default Register;

内容的提问来源于stack exchange,提问作者Zulqarnain Ansari

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 14:10:34