Flutter for OpenHarmony社团管理App实战:意见反馈实现

用户反馈是产品改进的重要来源。社团成员用着不爽了想吐槽,发现Bug了想报告,有好建议想提,都需要一个渠道。今天我们就来实现一个意见反馈页面,让用户的声音能被听到。

这个页面要做的事情:提交反馈、查看历史反馈、显示官方回复。

引入依赖

dart 复制代码
import 'package:flutter/material.dart';

Material库提供基础UI组件,每个页面都要引入。

没它啥也干不了。

dart 复制代码
import 'package:provider/provider.dart';

Provider做状态管理,反馈数据从这里拿。

数据变了页面自动刷新。

dart 复制代码
import 'package:intl/intl.dart';

intl库用来格式化日期,显示反馈时间要用到。

这个库功能挺强大的。

dart 复制代码
import '../../providers/app_provider.dart';
import '../../models/user.dart';

我们自己的Provider和数据模型。

UserFeedback模型定义在user.dart里。

页面基本结构

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

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

用StatefulWidget因为要管理TabController。

Tab切换需要状态。

状态类定义

dart 复制代码
class _FeedbackPageState extends State<FeedbackPage> 
    with SingleTickerProviderStateMixin {
  late TabController _tabController;

SingleTickerProviderStateMixin提供动画需要的vsync。

TabController需要这个。

初始化TabController

dart 复制代码
  @override
  void initState() {
    super.initState();
    _tabController = TabController(
      length: 2, 
      vsync: this
    );
  }

initState里创建TabController,length是Tab数量。

两个Tab:提交反馈、我的反馈。

销毁资源

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

dispose里销毁TabController,避免内存泄漏。

这是标准做法,别忘了。

构建页面

dart 复制代码
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('意见反馈'),
        bottom: TabBar(
          controller: _tabController,
          indicatorColor: Colors.white,
          tabs: const [
            Tab(text: '提交反馈'),
            Tab(text: '我的反馈'),
          ],
        ),
      ),

AppBar里嵌入TabBar,实现顶部标签导航。

indicatorColor设置指示器颜色。

TabBarView

dart 复制代码
      body: TabBarView(
        controller: _tabController,
        children: [
          _buildSubmitFeedback(context),
          _buildMyFeedbacks(context),
        ],
      ),
    );
  }

TabBarView和TabBar配合使用。

两个子页面分别是提交表单和历史列表。

提交反馈表单

dart 复制代码
  Widget _buildSubmitFeedback(BuildContext context) {
    final contentController = TextEditingController();
    String selectedType = '建议';

    return StatefulBuilder(
      builder: (context, setState) {
        return SingleChildScrollView(
          padding: const EdgeInsets.all(16),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [

StatefulBuilder让局部状态可以更新。

selectedType记录选中的反馈类型。

反馈类型标题

dart 复制代码
              const Text(
                '反馈类型', 
                style: TextStyle(
                  fontWeight: FontWeight.bold, 
                  fontSize: 16
                )
              ),
              const SizedBox(height: 12),

标题用粗体,下面留点间距。

让用户知道这是选择反馈类型的区域。

类型选择器

dart 复制代码
              Wrap(
                spacing: 12,
                children: ['建议', '投诉', 'Bug', '其他'].map((type) {
                  final isSelected = selectedType == type;
                  return ChoiceChip(
                    label: Text(type),
                    selected: isSelected,
                    selectedColor: const Color(0xFF4A90E2).withOpacity(0.2),
                    onSelected: (selected) {
                      setState(() {
                        selectedType = type;
                      });
                    },
                  );
                }).toList(),
              ),

ChoiceChip做单选,Wrap自动换行。

选中的用蓝色背景。

反馈内容标题

dart 复制代码
              const SizedBox(height: 24),
              const Text(
                '反馈内容', 
                style: TextStyle(
                  fontWeight: FontWeight.bold, 
                  fontSize: 16
                )
              ),
              const SizedBox(height: 12),

和类型标题一样的样式。

保持视觉一致性。

内容输入框

dart 复制代码
              TextField(
                controller: contentController,
                maxLines: 6,
                decoration: InputDecoration(
                  hintText: '请详细描述您的问题或建议...',
                  border: OutlineInputBorder(
                    borderRadius: BorderRadius.circular(8)
                  ),
                ),
              ),

多行输入框,6行高度。

hintText给个提示。

提交按钮

dart 复制代码
              const SizedBox(height: 24),
              SizedBox(
                width: double.infinity,
                height: 48,
                child: ElevatedButton(
                  style: ElevatedButton.styleFrom(
                    backgroundColor: const Color(0xFF4A90E2),
                    shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.circular(24)
                    ),
                  ),

按钮占满宽度,圆角设计。

蓝色背景很醒目。

提交逻辑

dart 复制代码
                  onPressed: () {
                    if (contentController.text.isEmpty) {
                      ScaffoldMessenger.of(context).showSnackBar(
                        const SnackBar(
                          content: Text('请输入反馈内容')
                        )
                      );
                      return;
                    }

先验证内容不为空。

空的话弹个SnackBar提示。

添加反馈

dart 复制代码
                    final provider = Provider.of<AppProvider>(
                      context, 
                      listen: false
                    );
                    provider.addFeedback(UserFeedback(
                      userId: provider.currentUser.id,
                      userName: provider.currentUser.name,
                      content: contentController.text,
                      type: selectedType,
                    ));

调用provider添加反馈。

传入用户信息和反馈内容。

提交成功处理

dart 复制代码
                    contentController.clear();
                    ScaffoldMessenger.of(context).showSnackBar(
                      const SnackBar(
                        content: Text('反馈提交成功')
                      )
                    );
                    _tabController.animateTo(1);
                  },
                  child: const Text(
                    '提交反馈', 
                    style: TextStyle(
                      fontSize: 16, 
                      color: Colors.white
                    )
                  ),
                ),
              ),
            ],
          ),
        );
      },
    );
  }

清空输入框,弹提示,切换到历史Tab。

animateTo(1)切换到第二个Tab。

历史反馈列表

dart 复制代码
  Widget _buildMyFeedbacks(BuildContext context) {
    return Consumer<AppProvider>(
      builder: (context, provider, _) {
        final feedbacks = provider.feedbacks.where(
          (f) => f.userId == provider.currentUser.id
        ).toList();

        if (feedbacks.isEmpty) {
          return const Center(
            child: Text(
              '暂无反馈记录', 
              style: TextStyle(color: Colors.grey)
            )
          );
        }

Consumer监听Provider,过滤当前用户的反馈。

空列表显示提示。

反馈列表构建

dart 复制代码
        return ListView.builder(
          padding: const EdgeInsets.all(16),
          itemCount: feedbacks.length,
          itemBuilder: (context, index) {
            final feedback = feedbacks[index];
            return Card(
              margin: const EdgeInsets.only(bottom: 12),
              child: Padding(
                padding: const EdgeInsets.all(16),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [

ListView.builder渲染列表。

每条反馈用Card包裹。

类型和状态标签

dart 复制代码
                    Row(
                      children: [
                        Container(
                          padding: const EdgeInsets.symmetric(
                            horizontal: 8, 
                            vertical: 4
                          ),
                          decoration: BoxDecoration(
                            color: const Color(0xFF4A90E2).withOpacity(0.1),
                            borderRadius: BorderRadius.circular(4),
                          ),
                          child: Text(
                            feedback.type, 
                            style: const TextStyle(
                              color: Color(0xFF4A90E2), 
                              fontSize: 12
                            )
                          ),
                        ),
                        const Spacer(),
                        _buildStatusBadge(feedback.status),
                      ],
                    ),

左边类型标签,右边状态标签。

Spacer把它们推到两端。

反馈内容

dart 复制代码
                    const SizedBox(height: 12),
                    Text(
                      feedback.content, 
                      style: const TextStyle(fontSize: 15)
                    ),
                    const SizedBox(height: 8),
                    Text(
                      DateFormat('yyyy-MM-dd HH:mm').format(
                        feedback.createTime
                      ), 
                      style: const TextStyle(
                        color: Colors.grey, 
                        fontSize: 12
                      )
                    ),

内容正常字号,时间灰色小字。

DateFormat格式化成年月日时分。

官方回复

dart 复制代码
                    if (feedback.reply.isNotEmpty) ...[
                      const SizedBox(height: 12),
                      Container(
                        padding: const EdgeInsets.all(12),
                        decoration: BoxDecoration(
                          color: Colors.grey[100],
                          borderRadius: BorderRadius.circular(8),
                        ),
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            const Text(
                              '官方回复:', 
                              style: TextStyle(
                                fontWeight: FontWeight.bold, 
                                fontSize: 13
                              )
                            ),
                            const SizedBox(height: 4),
                            Text(
                              feedback.reply, 
                              style: const TextStyle(fontSize: 13)
                            ),
                          ],
                        ),
                      ),
                    ],
                  ],
                ),
              ),
            );
          },
        );
      },
    );
  }

有回复才显示回复区域。

灰色背景区分开来。

状态标签组件

dart 复制代码
  Widget _buildStatusBadge(String status) {
    Color color;
    switch (status) {
      case '已回复':
        color = Colors.green;
        break;
      case '处理中':
        color = Colors.orange;
        break;
      default:
        color = Colors.grey;
    }

根据状态设置颜色。

已回复绿色,处理中橙色,待处理灰色。

标签容器

dart 复制代码
    return Container(
      padding: const EdgeInsets.symmetric(
        horizontal: 8, 
        vertical: 4
      ),
      decoration: BoxDecoration(
        color: color.withOpacity(0.1),
        borderRadius: BorderRadius.circular(4),
      ),
      child: Text(
        status, 
        style: TextStyle(
          color: color, 
          fontSize: 12
        )
      ),
    );
  }
}

浅色背景配合深色文字。

视觉效果统一。

反馈数据模型

dart 复制代码
class UserFeedback {
  final String id;
  final String userId;
  final String userName;
  final String content;
  final String type;
  final String status;
  final String reply;
  final DateTime createTime;
  
  UserFeedback({
    String? id,
    required this.userId,
    required this.userName,
    required this.content,
    required this.type,
    this.status = '待处理',
    this.reply = '',
    DateTime? createTime,
  }) : id = id ?? DateTime.now().millisecondsSinceEpoch.toString(),
       createTime = createTime ?? DateTime.now();
}

反馈模型包含这些字段。

id和createTime有默认值。

Provider中的反馈方法

dart 复制代码
void addFeedback(UserFeedback feedback) {
  _feedbacks.insert(0, feedback);
  notifyListeners();
}

void replyFeedback(
  String feedbackId, 
  String reply
) {
  final index = _feedbacks.indexWhere(
    (f) => f.id == feedbackId
  );
  if (index != -1) {
    _feedbacks[index] = UserFeedback(
      id: _feedbacks[index].id,
      userId: _feedbacks[index].userId,
      userName: _feedbacks[index].userName,
      content: _feedbacks[index].content,
      type: _feedbacks[index].type,
      status: '已回复',
      reply: reply,
      createTime: _feedbacks[index].createTime,
    );
    notifyListeners();
  }
}

addFeedback添加反馈,replyFeedback回复反馈。

回复时状态改成已回复。

测试数据

dart 复制代码
void initTestFeedbacks(AppProvider provider) {
  provider.addFeedback(UserFeedback(
    userId: 'user001',
    userName: '张三',
    content: '建议增加活动日历功能,方便查看近期活动安排。',
    type: '建议',
    status: '已回复',
    reply: '感谢您的建议,我们已经在开发中,预计下个版本上线。',
  ));

开发阶段用测试数据调试。

这条有官方回复。

dart 复制代码
  provider.addFeedback(UserFeedback(
    userId: 'user001',
    userName: '张三',
    content: '活动报名页面加载有点慢,希望能优化一下。',
    type: 'Bug',
    status: '处理中',
  ));
}

这条正在处理中。

页面上会显示橙色标签。

页面路由

dart 复制代码
Navigator.push(
  context,
  MaterialPageRoute(
    builder: (_) => const FeedbackPage()
  ),
);

从其他页面跳转过来就这么写。

标准路由跳转。

在个人中心添加入口

dart 复制代码
ListTile(
  leading: const Icon(
    Icons.feedback,
    color: Color(0xFF4A90E2)
  ),
  title: const Text('意见反馈'),
  trailing: const Icon(Icons.chevron_right),
  onTap: () {
    Navigator.push(
      context,
      MaterialPageRoute(
        builder: (_) => const FeedbackPage()
      ),
    );
  },
),

个人中心放个入口,点击跳转。

feedback图标很形象。

小结

意见反馈页面功能完整,提交表单、历史列表、官方回复都有了。ChoiceChip做类型选择很方便,StatefulBuilder让局部状态更新更灵活。

代码里用到的技巧:TabController管理标签页、ChoiceChip做单选、条件渲染显示回复。这些在其他场景也经常用到。

实际项目中可能还要加图片上传、反馈分类筛选这些功能,但基本框架就是这样。


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

相关推荐
错把套路当深情2 小时前
android两种渠道支持一键打包 + 随意组合各种渠道
android
彬sir哥3 小时前
android studio如何把.gradle从C盘移到D盘
android·gradle·maven·android studio
kirk_wang3 小时前
Flutter艺术探索-Flutter渲染优化:Widget生命周期与性能分析
flutter·移动开发·flutter教程·移动开发教程
南村群童欺我老无力.3 小时前
Flutter 框架跨平台鸿蒙开发 - 校园生活一站式:打造智慧校园服务平台
flutter·华为·harmonyos
IT陈图图4 小时前
跨端一致的交互体验实践:基于 Flutter × OpenHarmony 的 AlertDialog 对话框示例解析
flutter·交互·鸿蒙·openharmony
、BeYourself5 小时前
TabLayout 与 ViewPager2 的基本使用
android·android-studio
南村群童欺我老无力.5 小时前
Flutter 框架跨平台鸿蒙开发 - 城市文创打卡:探索城市文化创意之旅
android·flutter·华为·harmonyos
[H*]5 小时前
Flutter框架跨平台鸿蒙开发——Hero自定义Tween详解
flutter
Madison-No75 小时前
【Linux】文件操作&&重定向原理
android·linux·运维