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

相关推荐
小蜜蜂嗡嗡15 小时前
flutter列表中实现置顶动画
flutter
段娇娇16 小时前
Android jetpack LiveData (三) 粘性数据(数据倒灌)问题分析及解决方案
android·android jetpack
用户20187928316716 小时前
TabLayout被ViewPager2遮盖部分导致Tab难选中
android
始持16 小时前
第十二讲 风格与主题统一
前端·flutter
始持16 小时前
第十一讲 界面导航与路由管理
flutter·vibecoding
法欧特斯卡雷特16 小时前
Kotlin 2.3.20 现已发布,来看看!
android·前端·后端
始持16 小时前
第十三讲 异步操作与异步构建
前端·flutter
闻哥16 小时前
深入理解 MySQL InnoDB Buffer Pool 的 LRU 冷热数据机制
android·java·jvm·spring boot·mysql·adb·面试
新镜16 小时前
【Flutter】 视频视频源横向、竖向问题
flutter
ii_best17 小时前
安卓/ios开发辅助软件按键精灵小精灵实现简单的UI多配置管理
android·ui·ios·自动化