717 lines
22 KiB
Dart
717 lines
22 KiB
Dart
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();
|
||
});
|
||
}
|
||
}
|