帮助与反馈页面开发
This commit is contained in:
BIN
assets/images/app_logo_gray_transparent.png
Normal file
BIN
assets/images/app_logo_gray_transparent.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.0 MiB |
716
lib/features/v2/my/presentation/pages/help_feedback_page.dart
Normal file
716
lib/features/v2/my/presentation/pages/help_feedback_page.dart
Normal file
@@ -0,0 +1,716 @@
|
|||||||
|
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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ import 'package:maibu_satabot_v2/features/v2/my/presentation/widgets/quick_actio
|
|||||||
import 'package:maibu_satabot_v2/features/v2/my/presentation/widgets/menu_item_card.dart';
|
import 'package:maibu_satabot_v2/features/v2/my/presentation/widgets/menu_item_card.dart';
|
||||||
import 'package:maibu_satabot_v2/features/v2/my/presentation/pages/system_settings_page.dart';
|
import 'package:maibu_satabot_v2/features/v2/my/presentation/pages/system_settings_page.dart';
|
||||||
import 'package:maibu_satabot_v2/features/v2/my/presentation/pages/profile_detail_page.dart';
|
import 'package:maibu_satabot_v2/features/v2/my/presentation/pages/profile_detail_page.dart';
|
||||||
|
import 'package:maibu_satabot_v2/features/v2/my/presentation/pages/help_feedback_page.dart';
|
||||||
|
|
||||||
/// 我的页面(100% 还原设计稿)
|
/// 我的页面(100% 还原设计稿)
|
||||||
class MyPage extends StatelessWidget {
|
class MyPage extends StatelessWidget {
|
||||||
@@ -75,90 +76,91 @@ class _MyPageContent extends StatelessWidget {
|
|||||||
|
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
// 用户信息卡片(延伸到状态栏)
|
// 用户信息卡片(延伸到状态栏)
|
||||||
UserProfileCard(
|
UserProfileCard(
|
||||||
name: userName,
|
name: userName,
|
||||||
role: userRole,
|
role: userRole,
|
||||||
avatar: user?.avatar,
|
avatar: user?.avatar,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// 跳转到个人信息详情页
|
// 跳转到个人信息详情页
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => const ProfileDetailPage(),
|
builder: (context) => const ProfileDetailPage(),
|
||||||
),
|
),
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
// 快捷入口卡片(负margin向上重叠)
|
|
||||||
Transform.translate(
|
|
||||||
offset: const Offset(0, -15),
|
|
||||||
child: QuickActionCard(
|
|
||||||
onMessageTap: () {
|
|
||||||
debugPrint('我的消息');
|
|
||||||
},
|
|
||||||
onFavoriteTap: () {
|
|
||||||
debugPrint('我的收藏');
|
|
||||||
},
|
|
||||||
onWorkOrderTap: () {
|
|
||||||
debugPrint('我的工单');
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
// 菜单列表(白色卡片)
|
|
||||||
Container(
|
|
||||||
margin: const EdgeInsets.only(
|
|
||||||
top: 15,
|
|
||||||
left: 16,
|
|
||||||
right: 16,
|
|
||||||
bottom: 16,
|
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: context.appColors.cardBackground,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: context.appColors.cardShadow,
|
|
||||||
blurRadius: 10,
|
|
||||||
offset: const Offset(0, 2),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: ListView.builder(
|
|
||||||
shrinkWrap: true,
|
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
// 末尾追加退出登录,占据“关于我们”原来的位置
|
|
||||||
itemCount: state.menuItems.length + 1,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
// 退出登录(列表最后一项,在可滚动区域内避免底部溢出)
|
|
||||||
if (index == state.menuItems.length) {
|
|
||||||
return _buildLogoutButton(context);
|
|
||||||
}
|
|
||||||
final item = state.menuItems[index];
|
|
||||||
// 🔥 夜间模式开关值以全局 ThemeCubit 状态为准
|
|
||||||
final displayItem = item.id == 'night_mode'
|
|
||||||
? item.copyWith(switchValue: isDarkMode)
|
|
||||||
: item;
|
|
||||||
return MenuItemCard(
|
|
||||||
item: displayItem,
|
|
||||||
onTap: () => _handleMenuItemTap(context, cubit, item),
|
|
||||||
onSwitchChanged: item.hasSwitch
|
|
||||||
? (value) {
|
|
||||||
// 更新菜单 UI 状态
|
|
||||||
cubit.toggleNightMode(value);
|
|
||||||
// 🔥 真正切换全局主题
|
|
||||||
context
|
|
||||||
.read<ThemeCubit>()
|
|
||||||
.toggleDarkMode(value);
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
// 快捷入口卡片(负margin向上重叠)
|
||||||
],
|
Transform.translate(
|
||||||
|
offset: const Offset(0, -15),
|
||||||
|
child: QuickActionCard(
|
||||||
|
onMessageTap: () {
|
||||||
|
debugPrint('我的消息');
|
||||||
|
},
|
||||||
|
onFavoriteTap: () {
|
||||||
|
debugPrint('我的收藏');
|
||||||
|
},
|
||||||
|
onWorkOrderTap: () {
|
||||||
|
debugPrint('我的工单');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// 菜单列表(白色卡片)
|
||||||
|
Container(
|
||||||
|
margin: const EdgeInsets.only(
|
||||||
|
top: 15,
|
||||||
|
left: 16,
|
||||||
|
right: 16,
|
||||||
|
bottom: 16,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: context.appColors.cardBackground,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: context.appColors.cardShadow,
|
||||||
|
blurRadius: 10,
|
||||||
|
offset: const Offset(0, 2),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: ListView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
// 末尾追加退出登录,占据“关于我们”原来的位置
|
||||||
|
itemCount: state.menuItems.length + 1,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
// 退出登录(列表最后一项,在可滚动区域内避免底部溢出)
|
||||||
|
if (index == state.menuItems.length) {
|
||||||
|
return _buildLogoutButton(context);
|
||||||
|
}
|
||||||
|
final item = state.menuItems[index];
|
||||||
|
// 🔥 夜间模式开关值以全局 ThemeCubit 状态为准
|
||||||
|
final displayItem = item.id == 'night_mode'
|
||||||
|
? item.copyWith(switchValue: isDarkMode)
|
||||||
|
: item;
|
||||||
|
return MenuItemCard(
|
||||||
|
item: displayItem,
|
||||||
|
onTap: () =>
|
||||||
|
_handleMenuItemTap(context, cubit, item),
|
||||||
|
onSwitchChanged: item.hasSwitch
|
||||||
|
? (value) {
|
||||||
|
// 更新菜单 UI 状态
|
||||||
|
cubit.toggleNightMode(value);
|
||||||
|
// 🔥 真正切换全局主题
|
||||||
|
context.read<ThemeCubit>().toggleDarkMode(
|
||||||
|
value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -196,6 +198,13 @@ class _MyPageContent extends StatelessWidget {
|
|||||||
MaterialPageRoute(builder: (context) => const SystemSettingsPage()),
|
MaterialPageRoute(builder: (context) => const SystemSettingsPage()),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
case 'help_feedback':
|
||||||
|
// 跳转到帮助与反馈页面
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(builder: (context) => const HelpFeedbackPage()),
|
||||||
|
);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
debugPrint('点击菜单: ${item.title}');
|
debugPrint('点击菜单: ${item.title}');
|
||||||
// TODO: 根据 ID 跳转到对应页面
|
// TODO: 根据 ID 跳转到对应页面
|
||||||
|
|||||||
Reference in New Issue
Block a user