Flutter for OpenHarmony垃圾分类指南App实战:意见反馈实现

前言

用户的反馈是产品改进的重要来源。意见反馈页面让用户可以方便地提交问题和建议,是App和用户沟通的桥梁。本文将详细介绍如何在Flutter for OpenHarmony环境下实现一个完整的意见反馈页面,包括表单设计、类型选择、输入验证以及提交逻辑等核心技术点。

技术要点概览

本页面涉及的核心技术点:

  • StatefulWidget:管理表单状态
  • TextEditingController:输入框控制器
  • ChoiceChip:反馈类型选择
  • 表单验证:输入内容的有效性检查
  • SingleChildScrollView:键盘弹出时内容滚动

为什么用StatefulWidget

这个页面需要管理两个状态:选中的反馈类型和输入框的内容:

dart 复制代码
class FeedbackPage extends StatefulWidget {
  const FeedbackPage({super.key});

  @override
  State<FeedbackPage> createState() => _FeedbackPageState();
}

class _FeedbackPageState extends State<FeedbackPage> {
  final _controller = TextEditingController();
  String _selectedType = '功能建议';

TextEditingController用来控制输入框,可以获取输入的内容,也可以清空输入框。_selectedType记录当前选中的反馈类型。

资源释放

dart 复制代码
  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

页面布局

页面分为三个部分:反馈类型选择、反馈内容输入、提交按钮:

dart 复制代码
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('意见反馈')),
      body: SingleChildScrollView(
        padding: EdgeInsets.all(16.w),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text('反馈类型', style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w500)),
            SizedBox(height: 12.h),

SingleChildScrollView包裹,这样键盘弹出时内容可以滚动,输入框不会被遮挡。

反馈类型选择

ChoiceChip组件实现类型选择:

dart 复制代码
            Wrap(
              spacing: 12.w,
              children: ['功能建议', '问题反馈', '内容纠错', '其他'].map((type) {
                return ChoiceChip(
                  label: Text(type),
                  selected: _selectedType == type,
                  selectedColor: AppTheme.primaryColor.withOpacity(0.2),
                  onSelected: (selected) {
                    if (selected) setState(() => _selectedType = type);
                  },
                );
              }).toList(),
            ),

组件选择ChoiceChip是Material Design提供的选择芯片组件,自带选中和未选中的样式,用起来很方便。Wrap组件让芯片自动换行,不会溢出屏幕。

反馈类型说明

四种反馈类型覆盖了常见的场景:

  • 功能建议:用户希望增加某个功能
  • 问题反馈:用户遇到了bug或问题
  • 内容纠错:垃圾分类信息有误
  • 其他:不属于以上类型的反馈

增强版类型选择

可以为每种类型添加图标和描述:

dart 复制代码
final feedbackTypes = [
  {'type': '功能建议', 'icon': Icons.lightbulb_outline, 'desc': '希望增加的功能'},
  {'type': '问题反馈', 'icon': Icons.bug_report, 'desc': '遇到的bug或问题'},
  {'type': '内容纠错', 'icon': Icons.edit, 'desc': '分类信息有误'},
  {'type': '其他', 'icon': Icons.more_horiz, 'desc': '其他类型的反馈'},
];

Widget _buildTypeSelector() {
  return Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: feedbackTypes.map((item) {
      final isSelected = _selectedType == item['type'];
      return Card(
        margin: EdgeInsets.only(bottom: 8.h),
        color: isSelected ? AppTheme.primaryColor.withOpacity(0.1) : null,
        child: ListTile(
          leading: Icon(
            item['icon'] as IconData,
            color: isSelected ? AppTheme.primaryColor : Colors.grey,
          ),
          title: Text(item['type'] as String),
          subtitle: Text(item['desc'] as String),
          trailing: isSelected ? Icon(Icons.check, color: AppTheme.primaryColor) : null,
          onTap: () => setState(() => _selectedType = item['type'] as String),
        ),
      );
    }).toList(),
  );
}

反馈内容输入

TextField组件实现多行文本输入:

dart 复制代码
            SizedBox(height: 24.h),
            Text('反馈内容', style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w500)),
            SizedBox(height: 12.h),
            TextField(
              controller: _controller,
              maxLines: 6,
              maxLength: 500,
              decoration: InputDecoration(
                hintText: '请详细描述您的问题或建议...',
                filled: true,
                fillColor: Colors.white,
                border: OutlineInputBorder(
                  borderRadius: BorderRadius.circular(12.r),
                  borderSide: BorderSide.none,
                ),
              ),
            ),

maxLines: 6让输入框显示6行高度,适合输入较长的内容。hintText给用户一个提示,告诉他们应该输入什么。

样式细节filled: true配合fillColor: Colors.white让输入框有白色背景。borderSide: BorderSide.none去掉边框线,看起来更简洁。

图片上传功能

有些问题用文字描述不清楚,让用户上传截图会更有帮助:

dart 复制代码
final _images = <File>[].obs;

Widget _buildImageUploader() {
  return Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      Text('上传截图(可选)', style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w500)),
      SizedBox(height: 12.h),
      Obx(() => Wrap(
        spacing: 8.w,
        runSpacing: 8.h,
        children: [
          ..._images.map((file) => _buildImageItem(file)),
          if (_images.length < 4) _buildAddButton(),
        ],
      )),
    ],
  );
}

Widget _buildImageItem(File file) {
  return Stack(
    children: [
      ClipRRect(
        borderRadius: BorderRadius.circular(8.r),
        child: Image.file(
          file,
          width: 80.w,
          height: 80.w,
          fit: BoxFit.cover,
        ),
      ),
      Positioned(
        top: 0,
        right: 0,
        child: GestureDetector(
          onTap: () => _images.remove(file),
          child: Container(
            padding: EdgeInsets.all(2.w),
            decoration: BoxDecoration(
              color: Colors.red,
              shape: BoxShape.circle,
            ),
            child: Icon(Icons.close, size: 14.sp, color: Colors.white),
          ),
        ),
      ),
    ],
  );
}

Widget _buildAddButton() {
  return GestureDetector(
    onTap: _pickImage,
    child: Container(
      width: 80.w,
      height: 80.w,
      decoration: BoxDecoration(
        color: Colors.grey.shade200,
        borderRadius: BorderRadius.circular(8.r),
        border: Border.all(color: Colors.grey.shade300, style: BorderStyle.solid),
      ),
      child: Icon(Icons.add_photo_alternate, size: 32.sp, color: Colors.grey),
    ),
  );
}

Future<void> _pickImage() async {
  final picker = ImagePicker();
  final image = await picker.pickImage(source: ImageSource.gallery);
  if (image != null) {
    _images.add(File(image.path));
  }
}

联系方式输入

可以让用户留下联系方式,方便后续沟通:

dart 复制代码
final _contactController = TextEditingController();

Widget _buildContactInput() {
  return Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      Text('联系方式(可选)', style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w500)),
      SizedBox(height: 12.h),
      TextField(
        controller: _contactController,
        decoration: InputDecoration(
          hintText: '请输入手机号或邮箱',
          filled: true,
          fillColor: Colors.white,
          prefixIcon: Icon(Icons.contact_mail),
          border: OutlineInputBorder(
            borderRadius: BorderRadius.circular(12.r),
            borderSide: BorderSide.none,
          ),
        ),
      ),
    ],
  );
}

提交按钮

dart 复制代码
            SizedBox(height: 32.h),
            SizedBox(
              width: double.infinity,
              child: ElevatedButton(
                onPressed: _submit,
                style: ElevatedButton.styleFrom(
                  backgroundColor: AppTheme.primaryColor,
                  padding: EdgeInsets.symmetric(vertical: 16.h),
                  shape: RoundedRectangleBorder(
                    borderRadius: BorderRadius.circular(12.r),
                  ),
                ),
                child: Text('提交反馈', style: TextStyle(fontSize: 16.sp, color: Colors.white)),
              ),
            ),
          ],
        ),
      ),
    );
  }

按钮宽度撑满屏幕,高度通过padding控制。圆角和输入框保持一致,视觉上更协调。

提交逻辑

提交前要验证用户是否输入了内容:

dart 复制代码
  bool _isSubmitting = false;
  
  void _submit() async {
    // 验证输入
    if (_controller.text.trim().isEmpty) {
      Get.snackbar('提示', '请输入反馈内容');
      return;
    }
    
    if (_controller.text.trim().length < 10) {
      Get.snackbar('提示', '反馈内容至少需要10个字符');
      return;
    }
    
    // 防止重复提交
    if (_isSubmitting) return;
    
    setState(() => _isSubmitting = true);
    
    try {
      // 上传图片
      final imageUrls = await _uploadImages();
      
      // 提交反馈
      await _submitFeedback(
        type: _selectedType,
        content: _controller.text.trim(),
        images: imageUrls,
        contact: _contactController.text.trim(),
      );
      
      Get.snackbar('成功', '感谢您的反馈!');
      Get.back();
    } catch (e) {
      Get.snackbar('错误', '提交失败,请重试');
    } finally {
      setState(() => _isSubmitting = false);
    }
  }
  
  Future<List<String>> _uploadImages() async {
    final urls = <String>[];
    for (var image in _images) {
      final url = await ApiService.uploadImage(image);
      urls.add(url);
    }
    return urls;
  }
  
  Future<void> _submitFeedback({
    required String type,
    required String content,
    required List<String> images,
    required String contact,
  }) async {
    await ApiService.submitFeedback({
      'type': type,
      'content': content,
      'images': images,
      'contact': contact,
      'deviceInfo': await _getDeviceInfo(),
      'appVersion': await _getAppVersion(),
    });
  }
}

验证逻辑说明

trim()去掉首尾空格,避免用户只输入空格就提交。提交成功后显示感谢提示,然后返回上一页。

草稿保存

用户输入到一半退出了,下次进来内容还在:

dart 复制代码
@override
void initState() {
  super.initState();
  _loadDraft();
}

Future<void> _loadDraft() async {
  final draft = await storage.read('feedback_draft');
  if (draft != null) {
    _controller.text = draft['content'] ?? '';
    _selectedType = draft['type'] ?? '功能建议';
    setState(() {});
  }
}

Future<void> _saveDraft() async {
  await storage.write('feedback_draft', {
    'content': _controller.text,
    'type': _selectedType,
  });
}

@override
void dispose() {
  _saveDraft();
  _controller.dispose();
  super.dispose();
}

历史记录

让用户可以查看自己提交过的反馈和处理状态:

dart 复制代码
Widget _buildHistoryButton() {
  return TextButton.icon(
    onPressed: () => Get.toNamed(Routes.feedbackHistory),
    icon: Icon(Icons.history),
    label: Text('历史反馈'),
  );
}

// 历史反馈页面
class FeedbackHistoryPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('历史反馈')),
      body: Obx(() {
        final list = controller.feedbackHistory;
        if (list.isEmpty) {
          return Center(child: Text('暂无反馈记录'));
        }
        
        return ListView.builder(
          itemCount: list.length,
          itemBuilder: (context, index) {
            final item = list[index];
            return Card(
              margin: EdgeInsets.all(8.w),
              child: ListTile(
                title: Text(item.type),
                subtitle: Text(item.content, maxLines: 2, overflow: TextOverflow.ellipsis),
                trailing: _buildStatusChip(item.status),
                onTap: () => _showDetail(item),
              ),
            );
          },
        );
      }),
    );
  }
  
  Widget _buildStatusChip(String status) {
    Color color;
    switch (status) {
      case 'pending':
        color = Colors.orange;
        break;
      case 'processing':
        color = Colors.blue;
        break;
      case 'resolved':
        color = Colors.green;
        break;
      default:
        color = Colors.grey;
    }
    
    return Chip(
      label: Text(_getStatusText(status), style: TextStyle(fontSize: 12.sp, color: Colors.white)),
      backgroundColor: color,
    );
  }
}

设备信息收集

收集设备信息有助于排查问题:

dart 复制代码
Future<Map<String, dynamic>> _getDeviceInfo() async {
  final deviceInfo = DeviceInfoPlugin();
  
  if (Platform.isAndroid) {
    final info = await deviceInfo.androidInfo;
    return {
      'platform': 'Android',
      'model': info.model,
      'version': info.version.release,
      'sdk': info.version.sdkInt,
    };
  } else if (Platform.isIOS) {
    final info = await deviceInfo.iosInfo;
    return {
      'platform': 'iOS',
      'model': info.model,
      'version': info.systemVersion,
    };
  }
  
  return {'platform': 'Unknown'};
}

性能优化

1. 使用const构造函数

dart 复制代码
const Text('反馈类型')
const Icon(Icons.add_photo_alternate, size: 32)

2. 防止重复提交

dart 复制代码
bool _isSubmitting = false;

void _submit() {
  if (_isSubmitting) return;
  _isSubmitting = true;
  // ...
}

总结

意见反馈页面是产品和用户沟通的重要渠道。做好这个功能,能帮助产品持续改进,也能让用户感受到被重视。本文介绍的实现方案包括:

  1. 表单设计:类型选择、内容输入、图片上传
  2. 输入验证:空值检查、长度检查
  3. 提交逻辑:防重复提交、错误处理
  4. 用户体验:草稿保存、历史记录

通过完善的反馈功能,可以建立起产品和用户之间的良好沟通渠道。


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

相关推荐
白雪落青衣31 分钟前
buuoj course 1详细解析
android
恋猫de小郭1 小时前
Android 发布全新性能分析器,实用性和性能大升级
android·前端·flutter
Kapaseker1 小时前
为什么 Java 的数组需要 new 出来
android·java·kotlin
黄林晴1 小时前
颠覆开发!Google AI Studio 一句话生成原生 Android App
android·google io
恋猫de小郭1 小时前
Flutter 3.44 发布啦,超级大版本更新!!!
android·flutter·ios
zb200641202 小时前
Laravel10.x重磅升级:新特性全解析
android
2601_957418802 小时前
深入解析Android相机有线连接:PTP与MTP协议栈实现原理与实践
android·数码相机·智能手机
张3蜂2 小时前
Flutter macOS 安装文档
flutter·macos
努力努力再努力wz2 小时前
【QT入门系列】QWidget 六大常用属性详解:windowOpacity、cursor、font、focus、toolTip 与 styleSheet
android·开发语言·数据结构·c++·qt·mysql·算法
撩得Android一次心动2 小时前
C语言基础笔记3【个人用】
android·c语言·开发语言·笔记