初始化
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hgdj/hj_utils/screen.dart';
|
||||
|
||||
class FeedbackQuestionCategoryView extends StatefulWidget {
|
||||
final Function(String category)? choseCategory;
|
||||
const FeedbackQuestionCategoryView({super.key, this.choseCategory});
|
||||
|
||||
@override
|
||||
State<FeedbackQuestionCategoryView> createState() =>
|
||||
_FeedbackQuestionCategoryViewState();
|
||||
}
|
||||
|
||||
class _FeedbackQuestionCategoryViewState
|
||||
extends State<FeedbackQuestionCategoryView> {
|
||||
final dataSource = [
|
||||
'账号问题',
|
||||
'影视资源',
|
||||
'APP体验',
|
||||
'播放失败',
|
||||
'播放卡顿',
|
||||
'分类有误',
|
||||
'充值问题',
|
||||
'其他'
|
||||
];
|
||||
String? selectCategory;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GridView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 4,
|
||||
mainAxisSpacing: 12,
|
||||
crossAxisSpacing: 5,
|
||||
childAspectRatio: 84 / 34),
|
||||
itemCount: dataSource.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final category = dataSource[index];
|
||||
final select = category == selectCategory;
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
selectCategory = category;
|
||||
setState(() {});
|
||||
widget.choseCategory?.call(category);
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: select
|
||||
? Color(0xFFF68804)
|
||||
: Colors.white.withValues(alpha: .05),
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
category,
|
||||
style: TextStyle(color: Colors.white, fontSize: 12),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class InfomationInputView extends StatefulWidget {
|
||||
final String title;
|
||||
final String hint;
|
||||
final TextEditingController controller;
|
||||
const InfomationInputView(this.controller,
|
||||
{super.key, this.title = '', this.hint = ''});
|
||||
|
||||
@override
|
||||
State<InfomationInputView> createState() => _InfomationInputViewState();
|
||||
}
|
||||
|
||||
class _InfomationInputViewState extends State<InfomationInputView> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
12.sizeBoxH,
|
||||
Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 90,
|
||||
child: Text(
|
||||
widget.title,
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.9),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
maxLines: null,
|
||||
style: TextStyle(color: Colors.white, fontSize: 12),
|
||||
maxLength: 20,
|
||||
controller: widget.controller,
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
hintText: widget.hint,
|
||||
hintStyle:
|
||||
TextStyle(color: Color(0xff525252), fontSize: 12),
|
||||
counterText: '',
|
||||
contentPadding: EdgeInsets.zero,
|
||||
isDense: true),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
12.sizeBoxH,
|
||||
Divider(
|
||||
height: 0.5,
|
||||
color: Colors.black.withValues(alpha: 0.05),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgdj/hj_utils/api_service/mine_service.dart';
|
||||
import 'package:hgdj/tools_base/toast.dart';
|
||||
import 'package:hgdj/tools_base/file_upload/file_upload_tool.dart';
|
||||
import 'package:hgdj/tools_base/loading/loading_alert_widget.dart';
|
||||
|
||||
class MineFeedbackLogic extends GetxController {
|
||||
/// 问题描述
|
||||
TextEditingController inputFeedback = TextEditingController();
|
||||
|
||||
/// 区域
|
||||
TextEditingController areaFeedback = TextEditingController();
|
||||
|
||||
/// 设备
|
||||
TextEditingController deviceFeedback = TextEditingController();
|
||||
|
||||
/// 网络运营商
|
||||
TextEditingController netFeedback = TextEditingController();
|
||||
|
||||
///联系方式
|
||||
TextEditingController contactFeedback = TextEditingController();
|
||||
|
||||
///封面本地地址
|
||||
List<String> _localPicList = [];
|
||||
|
||||
String _questionCategory = '';
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
inputFeedback.dispose();
|
||||
areaFeedback.dispose();
|
||||
deviceFeedback.dispose();
|
||||
netFeedback.dispose();
|
||||
contactFeedback.dispose();
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
/// 主要问题
|
||||
updateQuestionCategory(String category) => _questionCategory = category;
|
||||
|
||||
/// 资源图片变化
|
||||
updateQuestionImages(List<String> images) => _localPicList = images;
|
||||
|
||||
Future<void> onSubmit() async {
|
||||
if (_questionCategory.isEmpty) {
|
||||
showToast("请选择遇到的问题分类");
|
||||
return;
|
||||
}
|
||||
if (inputFeedback.text.isEmpty) {
|
||||
showToast("请填写问题描述");
|
||||
return;
|
||||
}
|
||||
|
||||
// 图片可选:无图直接提交;有图先上传再提交,上传失败仍照常提交(不阻断反馈)
|
||||
if (_localPicList.isEmpty) {
|
||||
_submitFeedback([]);
|
||||
return;
|
||||
}
|
||||
FileUploadTool().uploadImagesWithProgress(
|
||||
_localPicList,
|
||||
onSuccess: (urls) => _submitFeedback(urls),
|
||||
onFailure: () => _submitFeedback([]),
|
||||
);
|
||||
}
|
||||
|
||||
/// 提交反馈([images] 为已上传的图片 url,可为空)
|
||||
Future<void> _submitFeedback(List<String> images) async {
|
||||
LoadingAlertWidget.show(title: "正在提交...");
|
||||
try {
|
||||
FocusScope.of(Get.context!).unfocus();
|
||||
final success = await MineService.feedback(inputFeedback.text,
|
||||
location: areaFeedback.text,
|
||||
device: deviceFeedback.text,
|
||||
carrier: netFeedback.text,
|
||||
img: images,
|
||||
fType: _questionCategory,
|
||||
contact: contactFeedback.text);
|
||||
if (success) {
|
||||
Get.back();
|
||||
showToast('提交成功');
|
||||
} else {
|
||||
showToast('提交失败');
|
||||
}
|
||||
} on DioException catch (e) {
|
||||
showToast(e.message ?? '');
|
||||
} catch (e) {
|
||||
showToast(e.toString());
|
||||
} finally {
|
||||
LoadingAlertWidget.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgdj/assets_tool/app_colors.dart';
|
||||
import 'package:hgdj/hj_utils/screen.dart';
|
||||
|
||||
import 'feedback_question_category_view.dart';
|
||||
import 'mine_feedback_logic.dart';
|
||||
import 'mine_qa_page.dart';
|
||||
import 'photo_manage_view.dart';
|
||||
|
||||
class MineFeedbackPage extends StatelessWidget {
|
||||
const MineFeedbackPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GetBuilder<MineFeedbackLogic>(
|
||||
init: MineFeedbackLogic(),
|
||||
builder: (controller) => Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('意见反馈'),
|
||||
actions: [
|
||||
InkWell(
|
||||
enableFeedback: false,
|
||||
onTap: () => Get.to(() => MineQAPage()),
|
||||
child: Text(
|
||||
'Q&A',
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.45),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
),
|
||||
16.sizeBoxW,
|
||||
],
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
keyboardDismissBehavior:
|
||||
ScrollViewKeyboardDismissBehavior.onDrag,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(left: 10, right: 10, top: 12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'遇到的问题',
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.9),
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
12.sizeBoxH,
|
||||
FeedbackQuestionCategoryView(
|
||||
choseCategory: controller.updateQuestionCategory,
|
||||
),
|
||||
24.sizeBoxH,
|
||||
Text(
|
||||
'问题描述(必填)',
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.9),
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600),
|
||||
),
|
||||
12.sizeBoxH,
|
||||
Container(
|
||||
constraints: BoxConstraints(minHeight: 160),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12, vertical: 11),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: .05),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
TextField(
|
||||
maxLines: 8,
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.9),
|
||||
fontSize: 12),
|
||||
maxLength: 300,
|
||||
controller: controller.inputFeedback,
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
hintText:
|
||||
'请详细描述您的问题,无效信息无法帮助技术人员排查问题。
有效信息:机型,系统,地区,问题症状...',
|
||||
hintStyle: TextStyle(
|
||||
color: Color(0xff525252), fontSize: 12),
|
||||
counterStyle: TextStyle(fontSize: 8),
|
||||
counter: SizedBox(),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
isDense: true),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
// 字数计数:只监听不接管所有权(释放归 MineFeedbackLogic.onClose)
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: controller.inputFeedback,
|
||||
builder: (_, value, __) => Text(
|
||||
'${value.text.length}/300',
|
||||
style: TextStyle(
|
||||
color:
|
||||
Colors.black.withValues(alpha: 0.5),
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
14.sizeBoxH,
|
||||
InfomationInputView(controller.areaFeedback,
|
||||
title: '所在地区', hint: '例:浙江杭州'),
|
||||
InfomationInputView(controller.deviceFeedback,
|
||||
title: '设备信息', hint: '例:苹果14'),
|
||||
InfomationInputView(controller.netFeedback,
|
||||
title: '网络运营商', hint: '电信/联通/移动'),
|
||||
InfomationInputView(controller.contactFeedback,
|
||||
title: '联系方式', hint: 'QQ/微信/邮箱等'),
|
||||
24.sizeBoxH,
|
||||
Text(
|
||||
'上传图片',
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.9),
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w700),
|
||||
),
|
||||
12.sizeBoxH,
|
||||
PhotoManageView(
|
||||
resourceOnchanged: controller.updateQuestionImages,
|
||||
),
|
||||
24.sizeBoxH,
|
||||
Text(
|
||||
'以方便我们给您回复,有效的改进建议,有惊喜赠送哟!',
|
||||
style:
|
||||
TextStyle(color: Color(0xff525252), fontSize: 12),
|
||||
),
|
||||
60.sizeBoxH,
|
||||
],
|
||||
),
|
||||
)),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: controller.onSubmit,
|
||||
child: Container(
|
||||
margin: EdgeInsets.symmetric(horizontal: 45),
|
||||
alignment: Alignment.center,
|
||||
height: 44,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
color: AppColors.actionRed,
|
||||
),
|
||||
child: Text(
|
||||
'提交意见',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600),
|
||||
),
|
||||
),
|
||||
),
|
||||
20.sizeBoxH,
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:easy_rich_text/easy_rich_text.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hgdj/assets_tool/app_colors.dart';
|
||||
import 'package:hgdj/hj_utils/screen.dart';
|
||||
import 'package:hgdj/routers/jump_router.dart';
|
||||
|
||||
///帮助反馈
|
||||
class MineQAPage extends StatefulWidget {
|
||||
const MineQAPage({super.key});
|
||||
@override
|
||||
State<StatefulWidget> createState() => _MineQAPageState();
|
||||
}
|
||||
|
||||
class _MineQAPageState extends State<MineQAPage> {
|
||||
final String tg = 'https://t.me/mu02guang';
|
||||
final String email = 'xzhan5555@gmail.com';
|
||||
final String url = 'XV9.FM';
|
||||
|
||||
late final questionList = [
|
||||
{
|
||||
"question": "描述文件安装失败?",
|
||||
"answer": "若提示【新的MDM有效负载与旧的有效负载不匹配】,请移除移动设备管理步骤:【设置-通用-设备管理-移动设备管理-移除管理】",
|
||||
'isOpen': true
|
||||
},
|
||||
{
|
||||
"question": "怎么找回账号?",
|
||||
"answer":
|
||||
"本平台登录会自动创建账号,需保存账号凭证或绑定手机号码,才能记录之前的账号信息。受行业限制,APP无法正常使用时需升级,未绑定手机号码会导致账号信息丢失。请及时绑定手机号码或保存账号凭证,以免VIP信息丢失,造成巨大财产损失!账号丢失的用户可在 『我的』页面-账号找回,原账号的VIP信息会转移至新账号上。『账号凭证』『手机绑定』都没有的情况下,如账号VIP信息丢失,可以提供VIP支付充值凭证截图联系在线客服为您查询核实恢复VIP。",
|
||||
'isOpen': false
|
||||
},
|
||||
{
|
||||
"question": "怎么支付不成功?",
|
||||
"answer":
|
||||
"1.因超时支付无法到账,请重新发起。\n2.每天发起支付不能超过5次,连续发起且未支付,账号可能被加入黑名单。\n3.支付通道在夜间比较忙碌,尝试多次发起,后台会为您自动切换不同支付通道。\n4.若充值成功,用户权益通常会在数分钟内到账,请刷新APP或重启。\n6.若支付成功并重启APP后依然没有到账,请联系在线客服并提供付款成功凭证截图。",
|
||||
'isOpen': false
|
||||
},
|
||||
{
|
||||
"question": "收到手机报毒提醒?",
|
||||
"answer":
|
||||
"本平台有主要收益为广告赞助,且保证APP安全无毒,因平台主要展示内容为色情属于特殊行业,某些杀毒软件会误报毒提醒,如遇此类提醒请忽略继续使用。",
|
||||
'isOpen': false
|
||||
},
|
||||
{
|
||||
"question": "联系方式?",
|
||||
"answer": "商务合作TG: $tg\n官方邮箱: $email\n永久下载地址: $url",
|
||||
'isOpen': false
|
||||
},
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text("常见问题"),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
padding: EdgeInsets.fromLTRB(16, 12, 16, 32),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
...List.generate(
|
||||
questionList.length,
|
||||
(index) => _getItem(index),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _getItem(int index) {
|
||||
final item = questionList[index];
|
||||
final isOpen = item["isOpen"] as bool;
|
||||
return InkWell(
|
||||
enableFeedback: false,
|
||||
onTap: () {
|
||||
item["isOpen"] = !isOpen;
|
||||
setState(() {});
|
||||
},
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
item["question"] as String,
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.9),
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
Spacer(),
|
||||
Transform.rotate(
|
||||
angle: isOpen ? (pi * -0.5) : 0,
|
||||
child: Icon(
|
||||
Icons.arrow_forward_ios_rounded,
|
||||
size: 14,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (isOpen) ...[
|
||||
10.sizeBoxH,
|
||||
EasyRichText(
|
||||
'${item["answer"] as String}',
|
||||
defaultStyle: TextStyle(
|
||||
color: Color(0xff989898),
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 12,
|
||||
),
|
||||
patternList: [
|
||||
EasyRichTextPattern(
|
||||
targetString: tg,
|
||||
style: TextStyle(color: AppColors.primaryHighColor),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
launchUrlToWeb(tg);
|
||||
},
|
||||
),
|
||||
EasyRichTextPattern(
|
||||
targetString: url,
|
||||
style: TextStyle(color: AppColors.primaryHighColor),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
launchUrlToWeb('https://$url');
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
12.sizeBoxH,
|
||||
Divider(
|
||||
height: .5,
|
||||
color: Colors.black.withValues(alpha: .04),
|
||||
),
|
||||
19.sizeBoxH,
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:image_pickers/image_pickers.dart';
|
||||
import 'package:hgdj/tools_base/widget/add_media_source_button.dart';
|
||||
|
||||
class PhotoManageView extends StatefulWidget {
|
||||
final Function(List<String> resources)? resourceOnchanged;
|
||||
final int max;
|
||||
const PhotoManageView({super.key, this.resourceOnchanged, this.max = 9});
|
||||
|
||||
@override
|
||||
State<PhotoManageView> createState() => _PhotoManageViewState();
|
||||
}
|
||||
|
||||
class _PhotoManageViewState extends State<PhotoManageView> {
|
||||
final dataSource = <String>[];
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GridView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 3,
|
||||
mainAxisSpacing: 8,
|
||||
crossAxisSpacing: 8,
|
||||
childAspectRatio: 1),
|
||||
itemCount: min(dataSource.length + 1, 9),
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
if (index == dataSource.length)
|
||||
return AddMediaSourceButton(
|
||||
isVideo: false,
|
||||
onTap: _addAlbumPhotos,
|
||||
backgroundColor: Colors.black.withValues(alpha: .04),
|
||||
);
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Stack(
|
||||
children: [
|
||||
Image.file(
|
||||
File(dataSource[index]),
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
Positioned(
|
||||
right: 6,
|
||||
top: 6,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
dataSource.removeAt(index);
|
||||
setState(() {});
|
||||
},
|
||||
child: Container(
|
||||
width: 20,
|
||||
height: 20,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: .8),
|
||||
borderRadius: BorderRadius.circular(10)),
|
||||
alignment: Alignment.center,
|
||||
child: const Icon(
|
||||
Icons.close,
|
||||
size: 14,
|
||||
),
|
||||
),
|
||||
))
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
_addAlbumPhotos() async {
|
||||
// image_picker 走系统相册 intent,选图不需要存储权限,直接调起
|
||||
final listMedia = await ImagePickers.pickerPaths(
|
||||
uiConfig: UIConfig(uiThemeColor: Colors.white),
|
||||
galleryMode: GalleryMode.image,
|
||||
selectCount: 9 - dataSource.length,
|
||||
showCamera: false,
|
||||
);
|
||||
if (listMedia.isEmpty) return;
|
||||
final ret = listMedia.map((e) => e.path ?? '').toList();
|
||||
dataSource.addAll(ret);
|
||||
setState(() {});
|
||||
widget.resourceOnchanged?.call(dataSource);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user