
用户反馈是产品改进的重要来源。社团成员用着不爽了想吐槽,发现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