初始化

This commit is contained in:
谢宇宁
2026-09-15 15:44:13 +07:00
commit a23ba685e9
1065 changed files with 101122 additions and 0 deletions
@@ -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);
}
}