HTTP 状态码是在这里插入图片描述
Web 开发绕不开的基础信息,但在真实项目里你很少会“背”它——更多时候是:调接口、看日志、对着 404/502 发呆,然后想快速查一下“它到底代表什么、该怎么处理”。

这一节我们做一个 HTTP 状态码速查页,放到 Web 开发助手 App 里:

  • 能搜索:输入 404forbidden 都能过滤
  • 能区分等级:2xx/3xx/4xx/5xx 用颜色快速定位
  • 能一键复制:点一下把状态码复制到剪贴板,方便写 issue 或排查记录

下面我按“项目里更常见的拆法”来写:数据结构、数据源、列表项、页面组合。每段代码都不长,方便你直接搬进工程里改。

1)先把状态码建模(别用 Map 乱飞)

import 'package:flutter/material.dart';

enum HttpStatusGroup {
  success,
  redirect,
  clientError,
  serverError,
}

class HttpStatusItem {
  const HttpStatusItem({
    required this.code,
    required this.name,
    required this.desc,
    required this.group,
  });

  final int code;
  final String name;
  final String desc;
  final HttpStatusGroup group;

  Color get color {
    switch (group) {
      case HttpStatusGroup.success:
        return Colors.green;
      case HttpStatusGroup.redirect:
        return Colors.blue;
      case HttpStatusGroup.clientError:
        return Colors.orange;
      case HttpStatusGroup.serverError:
        return Colors.red;
    }
  }
}

这里有意不用 Map<String, dynamic>

  • [更稳] 字段是强类型,code 就是 int,不会出现 '200'200 混着用
  • [更好扩展] 以后你想加“常见处理建议”“是否可重试”等字段,不用全局搜 key
  • [颜色规则集中] colorgroup 绑定,页面层不需要再写一堆 if/else

2)准备一份“可维护”的数据源

const httpStatusItems = <HttpStatusItem>[
  HttpStatusItem(code: 200, name: 'OK', desc: '请求成功', group: HttpStatusGroup.success),
  HttpStatusItem(code: 201, name: 'Created', desc: '资源已创建', group: HttpStatusGroup.success),
  HttpStatusItem(code: 204, name: 'No Content', desc: '无内容', group: HttpStatusGroup.success),

  HttpStatusItem(code: 301, name: 'Moved Permanently', desc: '永久重定向', group: HttpStatusGroup.redirect),
  HttpStatusItem(code: 302, name: 'Found', desc: '临时重定向', group: HttpStatusGroup.redirect),
  HttpStatusItem(code: 304, name: 'Not Modified', desc: '未修改', group: HttpStatusGroup.redirect),

  HttpStatusItem(code: 400, name: 'Bad Request', desc: '错误的请求', group: HttpStatusGroup.clientError),
  HttpStatusItem(code: 401, name: 'Unauthorized', desc: '未授权', group: HttpStatusGroup.clientError),
  HttpStatusItem(code: 403, name: 'Forbidden', desc: '禁止访问', group: HttpStatusGroup.clientError),
  HttpStatusItem(code: 404, name: 'Not Found', desc: '未找到', group: HttpStatusGroup.clientError),

  HttpStatusItem(code: 500, name: 'Internal Server Error', desc: '服务器错误', group: HttpStatusGroup.serverError),
  HttpStatusItem(code: 502, name: 'Bad Gateway', desc: '网关错误', group: HttpStatusGroup.serverError),
  HttpStatusItem(code: 503, name: 'Service Unavailable', desc: '服务不可用', group: HttpStatusGroup.serverError),
];

这份数据放哪更合适?我一般会放到 lib/features/http_status/data/http_status_items.dart 这类位置:

  • [集中管理] UI 页只负责展示和交互,不负责“知识库内容”
  • [便于复用] 之后你做“网络错误提示组件”,也可以直接引用同一份列表

3)列表项做成独立组件,并加一个“点一下复制”

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';

class HttpStatusTile extends StatelessWidget {
  const HttpStatusTile({super.key, required this.item});

  final HttpStatusItem item;

  
  Widget build(BuildContext context) {
    return Card(
      margin: EdgeInsets.only(bottom: 12.h),
      child: ListTile(
        onTap: () async {
          await Clipboard.setData(ClipboardData(text: item.code.toString()));
          if (!context.mounted) return;
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(content: Text('已复制:${item.code}')),
          );
        },
        leading: Container(
          width: 50.w,
          height: 50.w,
          decoration: BoxDecoration(
            color: item.color.withOpacity(0.2),
            borderRadius: BorderRadius.circular(8.r),
          ),
          alignment: Alignment.center,
          child: Text(
            '${item.code}',
            style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.bold, color: item.color),
          ),
        ),
        title: Text(item.name, style: const TextStyle(fontWeight: FontWeight.bold)),
        subtitle: Text(item.desc),
      ),
    );
  }
}

这个组件里我做了两件很“工程向”的小事:

  • [交互更顺手] 点列表项就复制状态码,比长按选中再复制省事
  • [不怕异步回调] context.mounted 这句是为了避免页面已退出时还弹 SnackBar(偶发但烦)

4)页面层:加搜索框 + 过滤逻辑

import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';

class HttpStatusPage extends StatefulWidget {
  const HttpStatusPage({super.key});

  
  State<HttpStatusPage> createState() => _HttpStatusPageState();
}

class _HttpStatusPageState extends State<HttpStatusPage> {
  final _controller = TextEditingController();

  
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    final keyword = _controller.text.trim().toLowerCase();

    final filtered = httpStatusItems.where((e) {
      if (keyword.isEmpty) return true;
      return e.code.toString().contains(keyword) ||
          e.name.toLowerCase().contains(keyword) ||
          e.desc.toLowerCase().contains(keyword);
    }).toList();

    return Scaffold(
      appBar: AppBar(title: const Text('HTTP状态码')),
      body: Padding(
        padding: EdgeInsets.all(16.w),
        child: Column(
          children: [
            TextField(
              controller: _controller,
              decoration: const InputDecoration(
                hintText: '搜索:如 404 / forbidden / 网关',
                prefixIcon: Icon(Icons.search),
              ),
              onChanged: (_) => setState(() {}),
            ),
            SizedBox(height: 12.h),
            Expanded(
              child: ListView.builder(
                itemCount: filtered.length,
                itemBuilder: (context, index) {
                  return HttpStatusTile(item: filtered[index]);
                },
              ),
            ),
          ],
        ),
      ),
    );
  }
}

搜索逻辑我写得比较“笨但好维护”:

  • [先满足常用场景] 输入数字查 code,输入英文查 name,输入中文查 desc
  • [少引入依赖] 不上复杂搜索库,够用就行(后面真要做全文索引再升级)
  • [避免大 rebuild] 这里只是小页面,setState 足够;如果列表数据很大再考虑 ValueListenableBuilder 或状态管理

5)别忘了 ScreenUtil 初始化(不然 .w/.h 不生效)

import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';

class App extends StatelessWidget {
  const App({super.key});

  
  Widget build(BuildContext context) {
    return ScreenUtilInit(
      designSize: const Size(360, 690),
      builder: (_, __) => const MaterialApp(
        home: HttpStatusPage(),
      ),
    );
  }
}

如果你项目里已经有 ScreenUtilInit,这里就不用重复包一层了。我把它单独拎出来是因为不少同学第一次抄代码会漏掉初始化,然后发现 16.w 怎么和 16 一样——其实是没初始化。


最后再补一句使用上的经验:状态码本身只是“结果”,真正定位问题时更关键的是请求链路(客户端参数/网关/后端服务/依赖服务)。这个页面更多是一个快速入口,能让你在排查时少打断一次思路。


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

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐