Flutter+开源鸿蒙实战|智安盾电商溯源平台Day3 溯源查询逻辑+鸿蒙网络请求适配

欢迎加入开源鸿蒙跨平台社区: https://openharmonycrossplatform.csdn.net。

摘要

本文基于Flutter + 开源鸿蒙跨端技术栈,继续讲解智安盾跨境电商溯源合规平台实战开发第三天内容。全程口语化讲解、弱化冗余代码、强化业务逻辑与鸿蒙避坑细节,重点带大家完成溯源查询核心逻辑开发、鸿蒙网络请求适配(解决网络权限、接口请求报错等高频坑),新增弹窗提示优化用户体验,实现“输入防伪码→查询溯源信息”的完整流程,同时确保多鸿蒙设备(手机、平板、DAYU200开发板)运行稳定,零基础也能轻松落地。

<!-- Schema.org 结构化数据 -->
<script type="application/ld+json">
{
  "@context":"https://schema.org",
  "@type":"BlogPosting",
  "headline":"Flutter+开源鸿蒙实战 智安盾Day3 溯源查询+鸿蒙网络请求适配",
  "author":{"@type":"Person","name":"鸿蒙跨端开发者"},
  "publisher":{"@type":"Organization","name":"CSDN开源鸿蒙跨平台社区"},
  "datePublished":"2026-04-29",
  "description":"Flutter+开源鸿蒙开发智安盾,Day3讲解溯源查询逻辑、鸿蒙网络请求适配、弹窗提示开发及常见坑解决",
  "keywords":"开源鸿蒙,OpenHarmony,Flutter跨端,智安盾,溯源查询,鸿蒙网络请求,避坑实录"
}
</script>

一、前言

哈喽大家好,Day2我们已经把智安盾的“门面”做好了——搭建了首页整体布局、溯源查询核心入口,还有全局导航栏、底部菜单栏,并且完成了鸿蒙多端适配,所有UI组件在手机、平板、开发板上都能正常显示、顺畅操作。

今天Day3,我们正式进入业务逻辑开发阶段,这是整个项目从“好看”到“好用”的关键一步!今天的核心任务非常明确,就是把Day2做好的“溯源查询入口”真正用起来:输入防伪码,点击查询,能成功获取溯源信息,同时处理各种异常情况(比如输入为空、查询失败、网络异常),还要重点解决鸿蒙设备网络请求的各种坑——毕竟鸿蒙的网络权限、接口适配,和普通Flutter开发不一样,踩过的坑我都会一一给大家讲清楚,帮大家少走弯路。

在这里插入图片描述

二、Day3 核心开发目标

今天的开发目标聚焦“溯源查询+网络请求”,不贪多、不复杂,确保每一步都能落地,同时避开鸿蒙网络开发的高频坑:

  1. 完成溯源查询核心逻辑开发,实现“输入防伪码→调用接口→获取溯源数据”的完整流程;
  2. 适配鸿蒙网络请求:配置鸿蒙专属网络权限、封装网络请求工具类,解决鸿蒙设备接口请求超时、报错问题;
  3. 新增3类弹窗提示:输入为空提示、查询成功提示、查询失败/网络异常提示,提升用户体验;
  4. 开发溯源结果展示页面雏形,用于显示查询到的商品溯源信息(简化版,后续优化);
  5. 完善页面跳转逻辑:首页查询→跳转至溯源结果页,适配鸿蒙多设备跳转流畅度;
  6. 多设备测试:确保在鸿蒙手机、平板、DAYU200开发板上,溯源查询功能正常、无报错、无卡顿;
  7. 总结鸿蒙网络请求的常见坑及解决方案,帮大家避开新手雷区。

三、前期准备

在开始今天的开发前,先确认3个关键前提,避免出现衔接问题,节省时间:

  1. 本地Flutter+鸿蒙开发环境正常,Day2开发的首页UI能正常运行,全局组件(按钮、输入框等)能正常复用;
  2. 已准备好测试用的“模拟接口”(没有真实接口也没关系,我会给大家提供模拟数据,直接能用);
  3. 测试设备(鸿蒙手机模拟器、真机、DAYU200开发板)已准备好,方便实时测试网络请求和查询功能;
  4. 确认项目目录规范,已创建lib/utils(工具类)、lib/models(数据模型)文件夹(Day1已创建,直接复用)。

这里重点提醒:鸿蒙设备的网络请求,必须单独配置网络权限,这是最容易踩的坑,今天我们第一步就解决这个问题,避免后续折腾半天找不到原因。

四、核心开发流程

4.1 第一步:配置鸿蒙专属网络权限(避坑重点!)

很多新手做鸿蒙跨端开发,都会遇到“接口请求一直超时、报错,却找不到原因”,其实90%都是因为没有配置鸿蒙专属的网络权限——我们之前做Flutter开发,只需要在AndroidManifest.xml里配置权限,但鸿蒙不继承Android的权限配置,必须在鸿蒙工程的module.json5里单独声明,这是鸿蒙网络开发的“专属坑”,一定要记住!

具体操作(复制就能用):
  1. 找到你的Flutter项目中的鸿蒙工程目录(通常是android/app/src/main/module.json5);
  2. requestPermissions数组中,添加鸿蒙网络权限配置,重点写清楚reason(权限申请说明),适配鸿蒙设备的权限弹窗,让用户知道“为什么需要网络权限”;

核心配置代码(仅放关键片段,直接复制替换/添加):

"requestPermissions": [
  {
    "name": "ohos.permission.INTERNET",
    "reason": "需要获取网络权限,才能查询商品溯源信息、合规检测数据",
    "usedScene": {
      "ability": ["com.zhiandun.app.MainAbility"],
      "when": "always"
    }
  }
]
避坑说明:
  • 不要漏写usedScene:鸿蒙权限配置必须指定使用场景,否则权限申请会失败,接口无法正常请求;
  • reason要通俗易懂:当鸿蒙设备弹出权限申请弹窗时,会显示这段文字,比如“需要获取网络权限,才能查询商品溯源信息”,用户一看就懂,不会误以为是恶意APP;
  • 配置完成后,重新运行项目:如果之前已经运行过,先停止运行,再重新启动,确保权限配置生效。

4.2 第二步:封装鸿蒙适配版网络请求工具类

我们创建一个全局网络请求工具类,适配鸿蒙环境,处理接口请求、异常捕获、弱网适配,后续合规检测、用户登录等功能,都能直接复用这个工具类,不用重复写代码。

核心设计思路(贴合鸿蒙场景):
  • 适配鸿蒙弱网环境:延长请求超时时间(从默认3秒改成8秒),避免长辈家、偏远地区网络波动导致请求失败;
  • 异常捕获到位:捕获网络异常、接口报错、数据解析失败等情况,统一返回错误信息,方便后续弹窗提示;
  • 简化调用:封装get请求方法,调用时只需要传入接口地址和参数,不用重复写请求配置;
  • 鸿蒙兼容:避免使用高版本依赖,防止在鸿蒙开发板上出现崩溃问题(参考Day1依赖版本,保持一致)。
具体实现(lib/utils/network_util.dart):
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';

// 鸿蒙适配版网络请求工具类(全局通用)
class NetworkUtil {
  // 单例模式,避免重复创建实例,节省资源
  static final NetworkUtil _instance = NetworkUtil._internal();
  factory NetworkUtil() => _instance;
  late Dio _dio;

  // 初始化网络配置(适配鸿蒙)
  NetworkUtil._internal() {
    _dio = Dio(BaseOptions(
      baseUrl: "https://api.zhiandun.com", // 模拟溯源接口地址(可替换为自己的接口)
      connectTimeout: const Duration(seconds: 8), // 延长超时,适配鸿蒙弱网
      receiveTimeout: const Duration(seconds: 8),
      headers: {
        "Content-Type": "application/json",
        "device-type": "openharmony", // 标记设备类型,方便后端区分鸿蒙设备
      },
    ));

    // 添加拦截器,捕获请求异常(鸿蒙端专属适配)
    _dio.interceptors.add(InterceptorsWrapper(
      onError: (DioException e, handler) {
        // 统一处理异常,比如网络断开、接口报错
        debugPrint("鸿蒙网络请求异常:${e.message}");
        handler.reject(e);
      },
    ));
  }

  // 封装get请求(用于溯源查询、合规检测等)
  Future<Map<String, dynamic>?> get(
    String path, {
    Map<String, dynamic>? params,
  }) async {
    try {
      final response = await _dio.get(path, queryParameters: params);
      // 接口返回成功(状态码200)
      if (response.statusCode == 200) {
        return response.data;
      } else {
        debugPrint("接口请求失败,状态码:${response.statusCode}");
        return null;
      }
    } catch (e) {
      debugPrint("鸿蒙网络请求失败:$e");
      return null;
    }
  }
}
依赖补充:

我们使用dio库做网络请求,Day1没有添加这个依赖,现在在pubspec.yaml中添加(注意版本,适配鸿蒙):

dependencies:
  flutter:
    sdk: flutter
  provider: ^6.1.1
  shared_preferences: ^2.2.2
  flutter_screenutil: ^5.9.0
  dio: ^5.4.0 # 鸿蒙官方兼容版,避免高版本崩溃

添加完成后,执行flutter pub get安装依赖,确保依赖拉取完整。

4.3 第三步:创建溯源数据模型(规范数据格式)

接口请求成功后,会返回溯源数据(比如商品名称、产地、物流信息、防伪状态等),我们创建一个数据模型,规范数据格式,避免数据解析混乱,同时适配鸿蒙端的数据解析逻辑(避免特殊字符导致解析失败)。

lib/models/trace_model.dart中创建模型:

// 溯源数据模型(适配鸿蒙数据解析,避免特殊字符报错)
class TraceModel {
  final String code; // 防伪码
  final String productName; // 商品名称
  final String origin; // 产地
  final String logisticsInfo; // 物流信息(简化版)
  final bool isGenuine; // 是否为正品
  final String createTime; // 溯源创建时间

  // 构造函数,必填字段
  TraceModel({
    required this.code,
    required this.productName,
    required this.origin,
    required this.logisticsInfo,
    required this.isGenuine,
    required this.createTime,
  });

  // 从JSON解析数据(核心:添加异常捕获,避免鸿蒙端解析失败)
  factory TraceModel.fromJson(Map<String, dynamic> json) {
    try {
      return TraceModel(
        code: json["code"] ?? "",
        productName: json["productName"] ?? "未知商品",
        origin: json["origin"] ?? "未知产地",
        logisticsInfo: json["logisticsInfo"] ?? "暂无物流信息",
        isGenuine: json["isGenuine"] ?? false,
        createTime: json["createTime"] ?? "",
      );
    } catch (e) {
      debugPrint("鸿蒙溯源数据解析失败:$e");
      // 解析失败时,返回默认值,避免APP崩溃
      return TraceModel(
        code: "",
        productName: "未知商品",
        origin: "未知产地",
        logisticsInfo: "暂无物流信息",
        isGenuine: false,
        createTime: "",
      );
    }
  }
}
避坑说明:
  • 所有字段都添加默认值:鸿蒙端数据解析时,如果接口返回的字段为空,没有默认值会导致APP崩溃,这是新手常踩的坑;
  • 添加异常捕获:如果接口返回特殊字符(比如中文乱码、特殊符号),解析时会报错,添加try-catch,确保APP不会崩溃,同时返回默认数据。

4.4 第四步:开发溯源查询核心逻辑(首页查询功能落地)

现在,我们把网络请求、数据模型和首页的溯源查询入口关联起来,实现“输入防伪码→点击查询→获取溯源数据→跳转结果页”的完整逻辑。

核心步骤:
  1. 在首页(home_page.dart)中,调用网络请求工具类,传入防伪码参数;
  2. 处理3种情况:输入为空、查询成功、查询失败/网络异常;
  3. 每种情况对应不同的弹窗提示,提升用户体验;
  4. 查询成功后,携带溯源数据,跳转至溯源结果页。
关键代码修改(首页查询逻辑,只放核心部分):
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:zhiandun_app/models/trace_model.dart';
import 'package:zhiandun_app/utils/network_util.dart';
import 'package:zhiandun_app/pages/trace_result_page.dart'; // 后续创建的结果页
// 其他导入省略...

class _HomePageState extends State<HomePage> {
  int _currentIndex = 0;
  final TextEditingController _codeController = TextEditingController();
  // 网络请求工具类实例
  final NetworkUtil _networkUtil = NetworkUtil();

  // 溯源查询核心方法(重点!)
  void _onQueryTap() async {
    String code = _codeController.text.trim();
    // 情况1:输入为空,弹窗提示
    if (code.isEmpty) {
      _showAlertDialog("提示", "请输入商品防伪码后再查询~");
      return;
    }

    // 情况2:输入不为空,发起网络请求(鸿蒙适配)
    _showLoadingDialog(); // 显示加载弹窗,避免用户重复点击

    try {
      // 调用溯源查询接口,传入防伪码参数
      final response = await _networkUtil.get(
        "/trace/query",
        params: {"code": code},
      );

      // 关闭加载弹窗
      Navigator.pop(context);

      // 情况2.1:查询成功,解析数据并跳转结果页
      if (response != null && response["code"] == 200) {
        TraceModel traceModel = TraceModel.fromJson(response["data"]);
        // 跳转至溯源结果页,携带溯源数据
        Navigator.push(
          context,
          MaterialPageRoute(
            builder: (context) => TraceResultPage(traceModel: traceModel),
          ),
        );
      } 
      // 情况2.2:查询失败(比如防伪码不存在)
      else {
        _showAlertDialog("查询失败", "未找到该防伪码对应的商品,请检查输入是否正确~");
      }
    } catch (e) {
      // 情况3:网络异常(比如断网、接口报错)
      Navigator.pop(context); // 关闭加载弹窗
      _showAlertDialog("网络异常", "网络连接失败,请检查网络设置后重试~");
    }
  }

  // 弹窗提示组件(通用,复用)
  void _showAlertDialog(String title, String content) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: Text(title, style: TextStyle(fontSize: 16.sp)),
        content: Text(content, style: TextStyle(fontSize: 14.sp)),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: const Text("确定"),
          ),
        ],
      ),
    );
  }

  // 加载弹窗(查询过程中显示)
  void _showLoadingDialog() {
    showDialog(
      context: context,
      barrierDismissible: false, // 禁止点击空白处关闭
      builder: (context) => const Center(
        child: CircularProgressIndicator(),
      ),
    );
  }

  // 其他代码(布局相关)省略...
}
关键讲解:
  • 加载弹窗:查询过程中显示加载动画,避免用户以为“点击没反应”,尤其是鸿蒙弱网环境下,请求耗时可能较长,加载提示很有必要;
  • 弹窗提示:针对不同异常情况,给出直白的提示,比如“请输入防伪码”“未找到该防伪码”,用户一看就懂,尤其是适合普通消费者、长辈使用;
  • 鸿蒙适配:网络请求过程中,即使出现异常,也会关闭加载弹窗,避免弹窗卡死,提升用户体验。

4.5 第五步:开发溯源结果展示页面(简化版)

查询成功后,需要跳转至结果页,展示商品的溯源信息(商品名称、产地、物流、防伪状态等),页面设计依旧贴合鸿蒙多端适配,风格简约、信息清晰,让用户快速获取核心溯源信息。

lib/pages/trace_result_page.dart中创建页面:

import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:zhiandun_app/models/trace_model.dart';
import 'package:zhiandun_app/widgets/app_bar.dart';

// 溯源结果展示页面(鸿蒙多端适配)
class TraceResultPage extends StatelessWidget {
  final TraceModel traceModel; // 接收溯源数据

  const TraceResultPage({super.key, required this.traceModel});

  
  Widget build(BuildContext context) {
    final isDayuOrTablet = MediaQuery.of(context).size.width >= 600;
    
    return Scaffold(
      // 复用全局导航栏,显示“溯源结果”标题,添加返回按钮
      appBar: CustomAppBar(title: '溯源结果', showBack: true),
      body: Padding(
        padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.h),
        child: SingleChildScrollView(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              // 防伪码信息
              _buildInfoItem("防伪码", traceModel.code),
              SizedBox(height: 16.h),
              // 商品名称
              _buildInfoItem("商品名称", traceModel.productName),
              SizedBox(height: 16.h),
              // 产地
              _buildInfoItem("商品产地", traceModel.origin),
              SizedBox(height: 16.h),
              // 物流信息
              _buildInfoItem("物流信息", traceModel.logisticsInfo),
              SizedBox(height: 16.h),
              // 防伪状态(重点突出,区分正品/假货)
              Container(
                width: double.infinity,
                padding: EdgeInsets.all(16.w),
                decoration: BoxDecoration(
                  color: traceModel.isGenuine ? Colors.green[100] : Colors.red[100],
                  borderRadius: BorderRadius.circular(8.r),
                ),
                child: Center(
                  child: Text(
                    traceModel.isGenuine ? "✅ 该商品为正品,可放心购买" : "❌ 该商品为假货,请注意防范",
                    style: TextStyle(
                      fontSize: isDayuOrTablet ? 18.sp : 16.sp,
                      fontWeight: FontWeight.bold,
                      color: traceModel.isGenuine ? Colors.green[700] : Colors.red[700],
                    ),
                  ),
                ),
              ),
              SizedBox(height: 20.h),
              // 溯源创建时间
              _buildInfoItem("溯源创建时间", traceModel.createTime),
            ],
          ),
        ),
      ),
    );
  }

  // 信息项组件(复用,统一风格)
  Widget _buildInfoItem(String title, String content) {
    final isDayuOrTablet = MediaQuery.of(context).size.width >= 600;
    
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(
          title,
          style: TextStyle(
            fontSize: isDayuOrTablet ? 16.sp : 14.sp,
            color: Colors.grey[500],
          ),
        ),
        SizedBox(height: 4.h),
        Text(
          content,
          style: TextStyle(
            fontSize: isDayuOrTablet ? 18.sp : 16.sp,
            fontWeight: FontWeight.w500,
          ),
        ),
      ],
    );
  }
}
鸿蒙适配细节:
  • 文字大小自适应:开发板/平板放大文字,确保可读性;
  • 布局适配:使用SingleChildScrollView,避免内容过多导致页面溢出,尤其是开发板横屏显示时;
  • 防伪状态突出:用不同颜色区分正品和假货,直观清晰,普通用户、长辈能快速识别。

4.6 第六步:多设备测试与常见坑解决(重点!)

所有逻辑开发完成后,我们在鸿蒙手机、平板、DAYU200开发板上测试,确保溯源查询功能正常,同时解决测试中可能遇到的高频坑:

常见坑1:鸿蒙设备请求超时,接口无法访问
  • 坑因:① 未配置鸿蒙专属网络权限;② 接口地址无法在鸿蒙设备上访问(比如本地接口,开发板未连接同一局域网);
  • 解决方案:① 重新检查module.json5中的网络权限配置,确保配置正确并重启项目;② 把接口替换为公开可访问的模拟接口,或确保开发板与电脑连接同一局域网。
常见坑2:查询成功后,跳转页面卡顿、闪退
  • 坑因:① 数据解析失败,导致页面渲染报错;② 鸿蒙开发板资源不足,组件嵌套过多;
  • 解决方案:① 检查TraceModel中的解析逻辑,确保所有字段都有默认值、异常捕获到位;② 简化结果页布局,减少组件嵌套,关闭多余动画。
常见坑3:输入防伪码后,点击查询无反应
  • 坑因:① 按钮触控区域不足,鸿蒙开发板点击不灵敏;② 输入框未获取焦点,输入内容未被正确获取;
  • 解决方案:① 检查自定义按钮的高度,确保≥48dp;② 优化输入框,确保输入内容能被正确获取(可添加打印日志,查看输入的防伪码是否正确)。
    在这里插入图片描述
测试重点:
  1. 输入为空,点击查询,是否弹出“请输入防伪码”提示;
  2. 输入正确的防伪码,是否能成功查询,跳转至结果页,显示正确的溯源信息;
  3. 输入错误的防伪码,是否弹出“未找到该防伪码”提示;
  4. 断开网络,点击查询,是否弹出“网络异常”提示;
  5. 所有操作在鸿蒙多设备上,是否流畅、无卡顿、无闪退。

五、Day3 开发总结(口语化复盘,理清思路)

今天Day3,我们终于实现了智安盾的核心功能——溯源查询,从“UI好看”做到了“功能好用”,整个过程重点解决了鸿蒙网络请求的各种坑,相信大家都有不少收获。

总结一下今天的核心收获:

  1. 学会了配置鸿蒙专属网络权限,避开了“请求超时”的高频坑,知道了鸿蒙权限和Android权限的区别;
  2. 封装了鸿蒙适配版网络请求工具类,实现了通用的get请求,后续其他功能可直接复用;
  3. 创建了溯源数据模型,掌握了鸿蒙端数据解析的技巧,避免了解析失败导致的APP崩溃;
  4. 完成了溯源查询的完整逻辑,包括输入校验、网络请求、弹窗提示、页面跳转;
  5. 开发了溯源结果展示页面,适配鸿蒙多设备,确保信息清晰、操作流畅;
  6. 总结了鸿蒙网络开发的3个常见坑及解决方案,为后续开发避开雷区。

这里提醒一句:今天的逻辑开发,重点在于“异常处理”和“鸿蒙适配”——一个好用的APP,不仅要能正常运行,还要能处理各种异常情况(比如断网、输入错误),尤其是鸿蒙设备,不同设备的兼容性不同,一定要多测试、多排查,才能保证功能稳定。
在这里插入图片描述

六、下期内容预告

Day4我们将会继续完善项目功能,重点做2件事:

  1. 开发合规检测核心功能,实现“输入商品文案→检测合规风险”的逻辑,适配鸿蒙网络请求;
  2. 完善个人中心页面框架,搭建用户登录入口,为后续积分成长、角色权限做准备;
  3. 优化鸿蒙多端适配细节,解决测试中发现的布局、触控问题;
  4. 新增数据缓存功能,避免重复请求接口,提升鸿蒙开发板运行流畅度。
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐