Files
flutterApp/lib/features/v2/my/presentation/pages/help_feedback_page.dart
2026-10-10 08:47:30 +08:00

717 lines
22 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
import 'package:maibu_satabot_v2/core/theme/AppTheme.dart';
/// 帮助与反馈页面
class HelpFeedbackPage extends StatefulWidget {
const HelpFeedbackPage({super.key});
@override
State<HelpFeedbackPage> createState() => _HelpFeedbackPageState();
}
class _HelpFeedbackPageState extends State<HelpFeedbackPage>
with SingleTickerProviderStateMixin {
late TabController _tabController;
final TextEditingController _feedbackController = TextEditingController();
final TextEditingController _contactController = TextEditingController();
int _selectedFeedbackType = 0;
List<String> _selectedImages = [];
static const _feedbackTypes = [
{'icon': Icons.lightbulb_outline, 'label': '功能建议'},
{'icon': Icons.bug_report_outlined, 'label': 'Bug报告'},
{'icon': Icons.sentiment_dissatisfied_outlined, 'label': '体验问题'},
{'icon': Icons.more_horiz, 'label': '其他'},
];
static const _faqList = [
{
'category': '设备绑定',
'questions': [
{
'q': '扫码绑定失败怎么办?',
'a': '请检查设备是否已开机并处于待绑定状态,确保手机蓝牙已开启且网络正常。如仍无法绑定,请尝试重启设备后重新扫码。'
},
{
'q': '搜不到设备蓝牙信号?',
'a': '请确认设备已开机且蓝牙指示灯正常闪烁。尝试靠近设备(1米以内)后重新搜索。如多个设备同时绑定,请逐一操作。'
},
]
},
{
'category': '路径规划',
'questions': [
{
'q': '无法生成路径?',
'a': '请确保已绘制完整的作业区域(至少3个打点),并检查GPS信号是否良好。如果使用自动规划模式,请确认区域边界没有自相交。'
},
{
'q': '地图不显示或定位偏移?',
'a': '请检查手机GPS是否已开启,并授权App位置权限。在室内或信号较弱的区域可能出现定位偏移,建议到开阔区域使用。'
},
]
},
{
'category': '远程控制',
'questions': [
{
'q': '远程视频卡顿/连接超时?',
'a': '请检查手机和设备的网络连接是否稳定。建议在4G/Wi-Fi信号良好的环境下使用远程控制功能。如持续卡顿,可尝试降低视频分辨率。'
},
{
'q': '急停功能怎么使用?',
'a': '在远程控制页面,点击屏幕中央红色急停按钮即可立即停止设备运行。急停后需手动解除才能恢复操作。'
},
]
},
{
'category': '工单系统',
'questions': [
{
'q': '如何提交工单?',
'a': '在「工单」页面点击右下角「新建工单」按钮,选择工单类型,填写问题描述并上传相关图片后提交即可。'
},
{
'q': '工单状态怎么看?',
'a': '在「工单」页面可查看所有已提交的工单及其当前状态(待处理/处理中/已完成)。点击具体工单可查看详情和回复。'
},
]
},
{
'category': '账号问题',
'questions': [
{
'q': '忘记密码怎么办?',
'a': '在登录页面点击「忘记密码」,通过注册手机号验证后即可重置密码。'
},
{
'q': '换设备登录需要重新绑定?',
'a': '更换手机登录同一账号后,设备绑定关系不会丢失。但需要在新设备上重新搜索和连接设备。'
},
]
},
];
@override
void initState() {
super.initState();
_tabController = TabController(length: 2, vsync: this);
}
@override
void dispose() {
_tabController.dispose();
_feedbackController.dispose();
_contactController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnnotatedRegion<SystemUiOverlayStyle>(
value: const SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarIconBrightness: Brightness.light,
),
child: Scaffold(
backgroundColor: context.appColors.pageBackground,
appBar: AppBar(
elevation: 0,
leading: IconButton(
icon: Icon(Icons.arrow_back, color: context.appColors.textPrimary),
onPressed: () => Navigator.pop(context),
),
title: Text(
AppLocalizations.of(context).translate('my_v2.help_feedback'),
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: context.appColors.textPrimary,
),
),
centerTitle: true,
bottom: TabBar(
controller: _tabController,
labelColor: const Color(0xFF165DFF),
unselectedLabelColor: context.appColors.textTertiary,
indicatorColor: const Color(0xFF165DFF),
indicatorSize: TabBarIndicatorSize.label,
tabs: const [
Tab(text: '常见问题'),
Tab(text: '意见反馈'),
],
),
),
body: TabBarView(
controller: _tabController,
children: [
_buildFAQTab(),
_buildFeedbackTab(),
],
),
),
);
}
// ==================== 常见问题 ====================
Widget _buildFAQTab() {
return ListView(
padding: const EdgeInsets.all(16),
children: [
..._faqList.map((category) => _buildFAQCategory(category)),
const SizedBox(height: 20),
_buildContactSection(),
],
);
}
Widget _buildFAQCategory(Map<String, dynamic> categoryData) {
final questions = categoryData['questions'] as List;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(top: 8, bottom: 8),
child: Text(
categoryData['category'] as String,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: context.appColors.textPrimary,
),
),
),
Container(
decoration: BoxDecoration(
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: context.appColors.cardShadow,
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Column(
children: questions.asMap().entries.map((entry) {
final index = entry.key;
final item = entry.value as Map<String, dynamic>;
final isLast = index == questions.length - 1;
return _buildFAQItem(
item['q'] as String,
item['a'] as String,
isLast,
);
}).toList(),
),
),
const SizedBox(height: 16),
],
);
}
Widget _buildFAQItem(String question, String answer, bool isLast) {
return Column(
children: [
ExpansionTile(
tilePadding: const EdgeInsets.symmetric(horizontal: 16),
childrenPadding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
shape: const Border(),
collapsedShape: const Border(),
title: Text(
question,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: context.appColors.textPrimary,
),
),
children: [
Text(
answer,
style: TextStyle(
fontSize: 13,
height: 1.6,
color: context.appColors.textSecondary,
),
),
],
),
if (!isLast)
Padding(
padding: const EdgeInsets.only(left: 48),
child: Divider(
height: 1,
thickness: 0.5,
color: context.appColors.divider,
),
),
],
);
}
// ==================== 联系客服 ====================
Widget _buildContactSection() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: context.appColors.cardShadow,
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'联系客服',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: context.appColors.textPrimary,
),
),
const SizedBox(height: 16),
_buildContactItem(
Icons.phone_outlined,
'客服热线',
'400-000-0000',
onTap: () => _showPhoneDialog('400-000-0000'),
),
const Divider(height: 24),
_buildContactItem(
Icons.qr_code_2,
'企业微信',
'扫码添加客服微信',
onTap: () => debugPrint('显示企业微信二维码'),
),
],
),
);
}
Widget _buildContactItem(
IconData icon,
String title,
String subtitle, {
VoidCallback? onTap,
}) {
return GestureDetector(
onTap: onTap,
behavior: HitTestBehavior.opaque,
child: Row(
children: [
Icon(icon, size: 22, color: context.appColors.textSecondary),
const SizedBox(width: 12),
Expanded(
child: Text(
title,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: context.appColors.textPrimary,
),
),
),
Text(
subtitle,
style: TextStyle(
fontSize: 13,
color: context.appColors.textTertiary,
),
),
const SizedBox(width: 6),
Icon(
Icons.chevron_right,
color: context.appColors.textTertiary,
size: 20,
),
],
),
);
}
// ==================== 意见反馈 ====================
Widget _buildFeedbackTab() {
return ListView(
padding: const EdgeInsets.all(16),
children: [
// 反馈类型
_buildSectionCard(
title: '反馈类型',
child: Wrap(
spacing: 8,
runSpacing: 8,
children: _feedbackTypes.asMap().entries.map((entry) {
final index = entry.key;
final type = entry.value;
final isSelected = _selectedFeedbackType == index;
return GestureDetector(
onTap: () => setState(() => _selectedFeedbackType = index),
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 14,
vertical: 8,
),
decoration: BoxDecoration(
color: isSelected
? const Color(0xFF165DFF).withOpacity(0.1)
: context.appColors.fillBackground,
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: isSelected
? const Color(0xFF165DFF)
: Colors.transparent,
width: 1,
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
type['icon'] as IconData,
size: 16,
color: isSelected
? const Color(0xFF165DFF)
: context.appColors.textSecondary,
),
const SizedBox(width: 6),
Text(
type['label'] as String,
style: TextStyle(
fontSize: 13,
color: isSelected
? const Color(0xFF165DFF)
: context.appColors.textSecondary,
fontWeight:
isSelected ? FontWeight.w600 : FontWeight.normal,
),
),
],
),
),
);
}).toList(),
),
),
const SizedBox(height: 16),
// 问题描述
_buildSectionCard(
title: '问题描述',
child: TextField(
controller: _feedbackController,
maxLines: 5,
maxLength: 500,
decoration: InputDecoration(
hintText: '请详细描述您遇到的问题或建议...',
hintStyle: TextStyle(
fontSize: 14,
color: context.appColors.textTertiary,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: context.appColors.divider),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: context.appColors.divider),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(
color: Color(0xFF165DFF),
width: 1.5,
),
),
filled: true,
fillColor: context.appColors.fillBackground,
contentPadding: const EdgeInsets.all(12),
),
),
),
const SizedBox(height: 16),
// 添加截图
_buildSectionCard(
title: '添加截图 (${_selectedImages.length}/6)',
child: Wrap(
spacing: 8,
runSpacing: 8,
children: [
..._selectedImages.asMap().entries.map((entry) {
return _buildImageItem(entry.key, entry.value);
}),
if (_selectedImages.length < 6) _buildAddImageButton(),
],
),
),
const SizedBox(height: 16),
// 联系方式
_buildSectionCard(
title: '联系方式(选填)',
child: TextField(
controller: _contactController,
decoration: InputDecoration(
hintText: '手机号/微信号,方便我们回访',
hintStyle: TextStyle(
fontSize: 14,
color: context.appColors.textTertiary,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: context.appColors.divider),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: context.appColors.divider),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(
color: Color(0xFF165DFF),
width: 1.5,
),
),
filled: true,
fillColor: context.appColors.fillBackground,
contentPadding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 14,
),
prefixIcon: const Icon(
Icons.phone_outlined,
size: 20,
color: Color(0xFF86909C),
),
),
),
),
const SizedBox(height: 24),
// 提交按钮
SizedBox(
width: double.infinity,
height: 48,
child: ElevatedButton(
onPressed: _submitFeedback,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF165DFF),
foregroundColor: Colors.white,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: const Text(
'提交反馈',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
),
),
),
const SizedBox(height: 32),
],
);
}
Widget _buildSectionCard({
required String title,
required Widget child,
}) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: context.appColors.cardShadow,
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: context.appColors.textPrimary,
),
),
const SizedBox(height: 12),
child,
],
),
);
}
Widget _buildImageItem(int index, String path) {
return Stack(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Image.file(
File(path),
width: 80,
height: 80,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) => Container(
width: 80,
height: 80,
color: context.appColors.fillBackground,
child: Icon(
Icons.broken_image_outlined,
color: context.appColors.textTertiary,
),
),
),
),
Positioned(
top: 2,
right: 2,
child: GestureDetector(
onTap: () => setState(() => _selectedImages.removeAt(index)),
child: Container(
padding: const EdgeInsets.all(2),
decoration: const BoxDecoration(
color: Colors.black54,
shape: BoxShape.circle,
),
child: const Icon(
Icons.close,
size: 14,
color: Colors.white,
),
),
),
),
],
);
}
Widget _buildAddImageButton() {
return GestureDetector(
onTap: _pickImage,
child: Container(
width: 80,
height: 80,
decoration: BoxDecoration(
color: context.appColors.fillBackground,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: context.appColors.divider,
width: 1,
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.add_photo_alternate_outlined,
size: 28,
color: context.appColors.textTertiary,
),
const SizedBox(height: 4),
Text(
'添加截图',
style: TextStyle(
fontSize: 11,
color: context.appColors.textTertiary,
),
),
],
),
),
);
}
// ==================== 交互方法 ====================
void _showPhoneDialog(String phoneNumber) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('客服热线'),
content: SelectableText(
phoneNumber,
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('关闭'),
),
TextButton(
onPressed: () {
Clipboard.setData(ClipboardData(text: phoneNumber));
Navigator.pop(context);
ScaffoldMessenger.of(this.context).showSnackBar(
const SnackBar(content: Text('号码已复制')),
);
},
child: const Text('复制号码'),
),
],
),
);
}
void _pickImage() {
// 占位:接入 image_picker 后实现
debugPrint('选择图片');
setState(() {
_selectedImages.add('placeholder_${_selectedImages.length}');
});
}
void _submitFeedback() {
if (_feedbackController.text.trim().isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('请输入问题描述'),
backgroundColor: Color(0xFFF53F3F),
),
);
return;
}
// TODO: 调用后端接口提交反馈
debugPrint('提交反馈: type=${_feedbackTypes[_selectedFeedbackType]['label']}, '
'content=${_feedbackController.text}, '
'images=${_selectedImages.length}, '
'contact=${_contactController.text}');
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('反馈已提交,感谢您的支持!'),
backgroundColor: context.appColors.success,
),
);
// 重置表单
_feedbackController.clear();
_contactController.clear();
setState(() {
_selectedFeedbackType = 0;
_selectedImages.clear();
});
}
}