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

求助:Flutter前端如何对接已实现的Django REST后端API?

Flutter 连接 Django REST Framework 实现注册功能方案

步骤1:添加网络请求依赖

在项目的pubspec.yaml中添加http包,用于发送API请求:

dependencies:
  flutter:
    sdk: flutter
  image_picker: ^1.0.4 # 你已有的图片选择依赖
  http: ^1.1.0

执行flutter pub get完成依赖安装。

步骤2:改造注册页面为有状态组件并添加输入控制

当前Regfirst是无状态组件,无法保存输入状态和处理异步请求,需要改为有状态组件,并为每个输入框绑定TextEditingController来获取用户输入:

import 'dart:io';
import 'package:flutter/services.dart';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:http/http.dart' as http;
import './register2.dart';

// 保留MyClip组件不变,此处省略重复代码

// 改造Avatar组件,添加回调传递选中的头像文件
class Avatar extends StatefulWidget {
  final Function(File?) onImageSelected;

  const Avatar({Key? key, required this.onImageSelected}) : super(key: key);

  @override
  State<Avatar> createState() => _AvatarState();
}

class _AvatarState extends State<Avatar> {
  File? image;
  Future pickImage() async {
    try {
      final image = await ImagePicker().pickImage(source: ImageSource.gallery);
      if (image == null) return;
      final imageTemp = File(image.path);
      setState(() => this.image = imageTemp);
      // 通知父组件更新头像
      widget.onImageSelected(imageTemp);
    } on PlatformException catch (e) {
      print('图片选择失败: $e');
    }
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: pickImage, // 添加点击事件触发图片选择
      child: Container(
        child: image != null
            ? ClipOval(
                child: Image.file(
                  image!,
                  width: 100,
                  height: 100,
                  fit: BoxFit.cover,
                ),
              )
            : Image.asset('assets/images/icon_sample.jpeg'),
      ),
    );
  }
}

// 改造Regfirst为有状态组件
class Regfirst extends StatefulWidget {
  @override
  State<Regfirst> createState() => _RegfirstState();
}

class _RegfirstState extends State<Regfirst> {
  // 输入控制器,用于获取每个输入框的值
  final _firstNameController = TextEditingController();
  final _lastNameController = TextEditingController();
  final _emailController = TextEditingController();
  final _phoneController = TextEditingController();
  final _passwordController = TextEditingController();
  final _confirmPasswordController = TextEditingController();
  
  File? _avatarImage;

  // 更新头像的回调方法
  void _updateAvatar(File? image) {
    setState(() {
      _avatarImage = image;
    });
  }

  // 注册API请求函数
  Future<void> _submitRegistration() async {
    // 先验证密码一致性
    if (_passwordController.text != _confirmPasswordController.text) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('两次输入的密码不一致')),
      );
      return;
    }

    // 替换为你的Django注册API地址
    const apiUrl = 'http://你的Django服务器IP:8000/api/register/';

    try {
      // 创建MultipartRequest,支持上传文件和表单数据
      var request = http.MultipartRequest('POST', Uri.parse(apiUrl));
      
      // 添加表单字段
      request.fields['first_name'] = _firstNameController.text;
      request.fields['last_name'] = _lastNameController.text;
      request.fields['email'] = _emailController.text;
      request.fields['phone'] = _phoneController.text;
      request.fields['password'] = _passwordController.text;
      
      // 如果用户选择了头像,添加文件字段
      if (_avatarImage != null) {
        request.files.add(
          await http.MultipartFile.fromPath(
            'avatar', // 对应Django后端接收头像的字段名
            _avatarImage!.path,
          ),
        );
      }

      // 发送请求
      var response = await request.send();
      
      // 处理响应结果
      if (response.statusCode == 201) {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text('注册成功!')),
        );
        Navigator.push(context, MaterialPageRoute(builder: (context) => Regsec()));
      } else {
        var errorMsg = await response.stream.bytesToString();
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('注册失败: $errorMsg')),
        );
      }
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('网络请求错误: $e')),
      );
    }
  }

  @override
  void dispose() {
    // 销毁控制器,避免内存泄漏
    _firstNameController.dispose();
    _lastNameController.dispose();
    _emailController.dispose();
    _phoneController.dispose();
    _passwordController.dispose();
    _confirmPasswordController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    var bg = 'assets/images/bg.jpeg';
    return Material(
      child: Container(
        decoration: BoxDecoration(
          image: DecorationImage(
            image: AssetImage(bg),
            fit: BoxFit.cover,
          ),
        ),
        child: Container(
          child: Stack(
            alignment: Alignment.center,
            children: [
              Positioned(
                top: 0,
                height: 400,
                width: 330,
                // 传递头像更新回调
                child: Avatar(onImageSelected: _updateAvatar),
              ),
              Positioned(
                top: 350,
                height: 300,
                width: 330,
                child: Column(
                  children: [
                    Expanded(
                      child: Row(
                        children: [
                          Expanded(
                            flex: 3,
                            child: TextField(
                              controller: _firstNameController,
                              decoration: InputDecoration(
                                hintText: "First name",
                                hintStyle: const TextStyle(
                                  color: Colors.white,
                                  fontSize: 25,
                                ),
                                labelStyle: const TextStyle(
                                    fontSize: 24, color: Colors.white),
                                border: const UnderlineInputBorder(),
                              ),
                            ),
                          ),
                          const SizedBox(width: 40),
                          Expanded(
                            flex: 3,
                            child: TextField(
                              controller: _lastNameController,
                              decoration: InputDecoration(
                                hintText: "Last name",
                                hintStyle: const TextStyle(
                                  color: Colors.white,
                                  fontSize: 25,
                                ),
                                labelStyle: const TextStyle(
                                    fontSize: 24, color: Colors.white),
                                border: const UnderlineInputBorder(),
                              ),
                            ),
                          ),
                        ],
                      ),
                    ),
                    Expanded(
                      child: Row(
                        children: [
                          Expanded(
                            flex: 3,
                            child: TextField(
                              controller: _emailController,
                              keyboardType: TextInputType.emailAddress,
                              decoration: InputDecoration(
                                hintText: "Email",
                                hintStyle: const TextStyle(
                                  color: Colors.white,
                                  fontSize: 25,
                                ),
                                labelStyle: const TextStyle(
                                    fontSize: 24, color: Colors.white),
                                border: const UnderlineInputBorder(),
                              ),
                            ),
                          ),
                          const SizedBox(width: 40),
                          Expanded(
                            flex: 3,
                            child: TextField(
                              controller: _phoneController,
                              keyboardType: TextInputType.phone,
                              decoration: InputDecoration(
                                hintText: "Phone number",
                                hintStyle: const TextStyle(
                                  color: Colors.white,
                                  fontSize: 25,
                                ),
                                labelStyle: const TextStyle(
                                    fontSize: 24, color: Colors.white),
                                border: const UnderlineInputBorder(),
                              ),
                            ),
                          ),
                        ],
                      ),
                    ),
                    Expanded(
                      child: Row(
                        children: [
                          Expanded(
                            flex: 3,
                            child: TextField(
                              controller: _passwordController,
                              obscureText: true,
                              decoration: InputDecoration(
                                hintText: "Password",
                                hintStyle: const TextStyle(
                                  color: Colors.white,
                                  fontSize: 25,
                                ),
                                labelStyle: const TextStyle(
                                    fontSize: 24, color: Colors.white),
                                border: const UnderlineInputBorder(),
                              ),
                            ),
                          ),
                        ],
                      ),
                    ),
                    Expanded(
                      child: Row(
                        children: [
                          Expanded(
                            flex: 3,
                            child: TextField(
                              controller: _confirmPasswordController,
                              obscureText: true,
                              decoration: InputDecoration(
                                hintText: "Confirm Password",
                                hintStyle: const TextStyle(
                                  color: Colors.white,
                                  fontSize: 25,
                                ),
                                labelStyle: const TextStyle(
                                    fontSize: 24, color: Colors.white),
                                border: const UnderlineInputBorder(),
                              ),
                            ),
                          ),
                        ],
                      ),
                    ),
                  ],
                ),
              ),
              Positioned(
                top: 680,
                height: 45,
                width: 250,
                child: ElevatedButton(
                  onPressed: _submitRegistration, // 绑定注册请求方法
                  child: const Text(
                    'Next',
                    style: TextStyle(fontSize: 30),
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

步骤3:Django后端配置

  1. 跨域请求配置:Flutter调试时会触发跨域请求,需要安装并配置django-cors-headers:
pip install django-cors-headers

在settings.py中添加以下配置:

INSTALLED_APPS = [
    ...
    'corsheaders',
]

MIDDLEWARE = [
    ...
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.common.CommonMiddleware',
]

# 调试环境允许所有来源,生产环境请指定具体域名
CORS_ALLOW_ALL_ORIGINS = True
  1. 序列化器支持文件上传:确保Django的序列化器能正确处理头像字段:
from rest_framework import serializers
from django.contrib.auth.models import User

class UserRegistrationSerializer(serializers.ModelSerializer):
    avatar = serializers.ImageField(required=False)

    class Meta:
        model = User
        fields = ['first_name', 'last_name', 'email', 'phone', 'password', 'avatar']
        extra_kwargs = {'password': {'write_only': True}}

    def create(self, validated_data):
        avatar = validated_data.pop('avatar', None)
        user = User.objects.create_user(**validated_data)
        if avatar:
            # 假设你的User模型有avatar字段,根据实际情况调整
            user.avatar = avatar
            user.save()
        return user

关键说明

  • 使用http.MultipartRequest处理包含文件的表单提交,这是上传图片的标准方式。
  • 每个输入框绑定TextEditingController,方便直接获取用户输入值。
  • 添加了密码一致性验证、网络错误捕获和用户反馈,提升交互体验。
  • Avatar组件通过回调将选中的图片传递给父组件,实现状态共享。

内容的提问来源于stack exchange,提问作者Zhichen Xu

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 20:45:35