19 changed files with 1362 additions and 337 deletions
Unified View
Diff Options
-
16lib/controller/home/report_controller.dart
-
23lib/controller/home/user_information_controller.dart
-
2lib/controller/message/chat_settings_controller.dart
-
247lib/controller/mine/real_feedback_controller.dart
-
85lib/controller/setting/blacklist_controller.dart
-
72lib/model/mine/blacklist_data.dart
-
17lib/network/api_urls.dart
-
5lib/network/home_api.dart
-
34lib/network/home_api.g.dart
-
22lib/network/user_api.dart
-
139lib/network/user_api.g.dart
-
6lib/pages/home/report_page.dart
-
2lib/pages/home/timeline_info.dart
-
2lib/pages/home/timeline_item.dart
-
3lib/pages/home/user_information_page.dart
-
339lib/pages/mine/feedback_page.dart
-
4lib/pages/mine/mine_page.dart
-
460lib/pages/mine/real_feedback_page.dart
-
221lib/pages/setting/blacklist_page.dart
@ -0,0 +1,247 @@ |
|||||
|
import 'dart:io'; |
||||
|
|
||||
|
import 'package:dating_touchme_app/network/user_api.dart'; |
||||
|
import 'package:dating_touchme_app/oss/oss_manager.dart'; |
||||
|
import 'package:flustars/flustars.dart'; |
||||
|
import 'package:flutter/material.dart'; |
||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; |
||||
|
import 'package:get/get.dart'; |
||||
|
import 'package:image_picker/image_picker.dart'; |
||||
|
import 'package:permission_handler/permission_handler.dart'; |
||||
|
|
||||
|
class RealFeedbackController extends GetxController { |
||||
|
final int type; |
||||
|
RealFeedbackController({required this.type}); |
||||
|
|
||||
|
|
||||
|
late UserApi _userApi; |
||||
|
final message = ''.obs; |
||||
|
final messageController = TextEditingController().obs; |
||||
|
|
||||
|
final tagList = <Map>[].obs; |
||||
|
|
||||
|
final active = 0.obs; |
||||
|
|
||||
|
final imgList = <String>[].obs; |
||||
|
|
||||
|
changeActive(int i){ |
||||
|
active.value = i; |
||||
|
} |
||||
|
|
||||
|
@override |
||||
|
void onInit() { |
||||
|
super.onInit(); |
||||
|
_userApi = Get.find<UserApi>(); |
||||
|
if(type == 1){ |
||||
|
tagList.value = [ |
||||
|
{"label": "账号异常", "value": 101}, |
||||
|
{"label": "违规与封禁", "value": 102}, |
||||
|
{"label": "头像/签名/实名认证", "value": 103}, |
||||
|
]; |
||||
|
} else if(type == 2){ |
||||
|
tagList.value = [ |
||||
|
{"label": "私信相关", "value": 201}, |
||||
|
{"label": "资料相关", "value": 202}, |
||||
|
{"label": "动态相关", "value": 203}, |
||||
|
{"label": "直播间相关", "value": 204}, |
||||
|
]; |
||||
|
} else if(type == 3){ |
||||
|
tagList.value = [ |
||||
|
{"label": "举报有人诈骗", "value": 301}, |
||||
|
{"label": "举报有人涉嫌诱导", "value": 302}, |
||||
|
{"label": "其他不良行为举报", "value": 303}, |
||||
|
{"label": "虚假宣传广告投诉", "value": 304}, |
||||
|
{"label": "未成年问题", "value": 305}, |
||||
|
{"label": "智能推荐相关", "value": 306}, |
||||
|
]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
// 选择头像 - 业务逻辑处理 |
||||
|
Future<void> handleCameraCapture() async { |
||||
|
try { |
||||
|
// 请求相机权限 |
||||
|
final ok = await _ensurePermission( |
||||
|
Permission.camera, |
||||
|
denyToast: '相机权限被拒绝,请在设置中允许访问相机', |
||||
|
); |
||||
|
if (!ok) return; |
||||
|
|
||||
|
// 请求麦克风权限(部分设备拍照/录像会一并请求建议预授权) |
||||
|
await _ensurePermission(Permission.microphone, denyToast: '麦克风权限被拒绝'); |
||||
|
|
||||
|
// 权限通过后拍照 |
||||
|
final ImagePicker picker = ImagePicker(); |
||||
|
final XFile? photo = await picker.pickImage(source: ImageSource.camera); |
||||
|
|
||||
|
if (photo != null) { |
||||
|
await processSelectedImage(File(photo.path)); |
||||
|
} |
||||
|
} catch (e) { |
||||
|
print('拍照失败: $e'); |
||||
|
// 更友好的错误提示 |
||||
|
if (e.toString().contains('permission') || e.toString().contains('权限')) { |
||||
|
SmartDialog.showToast('相机权限被拒绝,请在设置中允许访问相机'); |
||||
|
} else if (e.toString().contains('camera') || |
||||
|
e.toString().contains('相机')) { |
||||
|
SmartDialog.showToast('设备没有可用的相机'); |
||||
|
} else { |
||||
|
SmartDialog.showToast('拍照失败,请重试'); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Future<void> handleGallerySelection() async { |
||||
|
try { |
||||
|
// 请求相册/照片权限 |
||||
|
// final ok = await _ensurePermission( |
||||
|
// Permission.photos, |
||||
|
// // Android 上 photos 等价于 storage/mediaLibrary,permission_handler 会映射 |
||||
|
// denyToast: '相册权限被拒绝,请在设置中允许访问相册', |
||||
|
|
||||
|
// ); |
||||
|
// if (!ok) return; |
||||
|
|
||||
|
// 从相册选择图片 |
||||
|
final ImagePicker picker = ImagePicker(); |
||||
|
final XFile? image = await picker.pickImage(source: ImageSource.gallery); |
||||
|
|
||||
|
if (image != null) { |
||||
|
await processSelectedImage(File(image.path)); |
||||
|
} |
||||
|
} catch (e) { |
||||
|
print('选择图片失败: $e'); |
||||
|
// 更友好的错误提示 |
||||
|
if (e.toString().contains('permission') || e.toString().contains('权限')) { |
||||
|
SmartDialog.showToast('相册权限被拒绝,请在设置中允许访问相册'); |
||||
|
} else { |
||||
|
SmartDialog.showToast('选择图片失败,请重试'); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Future<void> handleMultiGallerySelection() async { |
||||
|
try { |
||||
|
// 请求相册/照片权限 |
||||
|
// final ok = await _ensurePermission( |
||||
|
// Permission.photos, |
||||
|
// // Android 上 photos 等价于 storage/mediaLibrary,permission_handler 会映射 |
||||
|
// denyToast: '相册权限被拒绝,请在设置中允许访问相册', |
||||
|
|
||||
|
// ); |
||||
|
// if (!ok) return; |
||||
|
|
||||
|
// 从相册选择图片 |
||||
|
final ImagePicker picker = ImagePicker(); |
||||
|
final List<XFile>? image = await picker.pickMultiImage(limit: 9 - imgList.length); |
||||
|
|
||||
|
if (image != null) { |
||||
|
final futures = image.map((e){ |
||||
|
return processSelectedMoreImage(File(e.path)); |
||||
|
}); |
||||
|
final list = await Future.wait(futures); |
||||
|
imgList.addAll(list); |
||||
|
print(imgList); |
||||
|
SmartDialog.dismiss(); |
||||
|
SmartDialog.showToast('上传相册成功'); |
||||
|
|
||||
|
|
||||
|
} |
||||
|
} catch (e) { |
||||
|
print('选择图片失败: $e'); |
||||
|
// 更友好的错误提示 |
||||
|
if (e.toString().contains('permission') || e.toString().contains('权限')) { |
||||
|
SmartDialog.showToast('相册权限被拒绝,请在设置中允许访问相册'); |
||||
|
} else { |
||||
|
SmartDialog.showToast('选择图片失败,请重试'); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 通用权限申请 |
||||
|
Future<bool> _ensurePermission(Permission permission, {String? denyToast}) async { |
||||
|
var status = await permission.status; |
||||
|
if (status.isGranted) return true; |
||||
|
|
||||
|
if (status.isDenied || status.isRestricted || status.isLimited) { |
||||
|
status = await permission.request(); |
||||
|
if (status.isGranted) return true; |
||||
|
if (denyToast != null) SmartDialog.showToast(denyToast); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if (status.isPermanentlyDenied) { |
||||
|
if (denyToast != null) SmartDialog.showToast('$denyToast,前往系统设置开启'); |
||||
|
// 延迟弹设置,避免与弹窗冲突 |
||||
|
Future.delayed(const Duration(milliseconds: 300), openAppSettings); |
||||
|
return false; |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
// 处理选中的图片 |
||||
|
Future<void> processSelectedImage(File imageFile) async { |
||||
|
try { |
||||
|
// 显示加载提示 |
||||
|
SmartDialog.showLoading(msg: '上传相册中...'); |
||||
|
String objectName = '${DateUtil.getNowDateMs()}.${imageFile.path.split('.').last}'; |
||||
|
String imageUrl = await OSSManager.instance.uploadFile(imageFile.readAsBytesSync(), objectName); |
||||
|
print('上传成功,图片URL: $imageUrl'); |
||||
|
imgList.add(imageUrl); |
||||
|
SmartDialog.dismiss(); |
||||
|
SmartDialog.showToast('相册上传成功'); |
||||
|
|
||||
|
|
||||
|
} catch (e) { |
||||
|
SmartDialog.dismiss(); |
||||
|
print('处理图片失败: $e'); |
||||
|
SmartDialog.showToast('上传相册失败,请重试'); |
||||
|
} |
||||
|
} |
||||
|
// 处理选中的图片 |
||||
|
Future<String> processSelectedMoreImage(File imageFile) async { |
||||
|
try { |
||||
|
// 显示加载提示 |
||||
|
SmartDialog.showLoading(msg: '上传相册中...'); |
||||
|
String objectName = '${DateUtil.getNowDateMs()}.${imageFile.path.split('.').last}'; |
||||
|
String imageUrl = await OSSManager.instance.uploadFile(imageFile.readAsBytesSync(), objectName); |
||||
|
print('上传成功,图片URL: $imageUrl'); |
||||
|
return imageUrl; |
||||
|
} catch (e) { |
||||
|
SmartDialog.dismiss(); |
||||
|
print('处理图片失败: $e'); |
||||
|
SmartDialog.showToast('上传相册失败,请重试'); |
||||
|
return ""; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
sendFeedback() async { |
||||
|
try { |
||||
|
final response = await _userApi.userCreateUserFeedback({ |
||||
|
"images": imgList.isNotEmpty ? imgList.join(",") : "", |
||||
|
"content": message.value, |
||||
|
"feedbackType": active.value, |
||||
|
}); |
||||
|
if (response.data.isSuccess) { |
||||
|
|
||||
|
SmartDialog.showToast('反馈已提交成功'); |
||||
|
Get.until((route) => route.isFirst); |
||||
|
} else { |
||||
|
|
||||
|
// 响应失败,抛出异常 |
||||
|
throw Exception(response.data.message ?? '获取数据失败'); |
||||
|
} |
||||
|
} catch(e){ |
||||
|
print('反馈提交失败: $e'); |
||||
|
SmartDialog.showToast('反馈提交失败'); |
||||
|
rethrow; |
||||
|
|
||||
|
} finally { |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,85 @@ |
|||||
|
import 'package:dating_touchme_app/network/user_api.dart'; |
||||
|
import 'package:easy_refresh/easy_refresh.dart'; |
||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; |
||||
|
import 'package:get/get.dart'; |
||||
|
import 'package:dating_touchme_app/model/mine/blacklist_data.dart'; |
||||
|
|
||||
|
class BlacklistController extends GetxController { |
||||
|
|
||||
|
late UserApi _userApi; |
||||
|
|
||||
|
final page = 1.obs; |
||||
|
final size = 10.obs; |
||||
|
|
||||
|
late final EasyRefreshController listRefreshController; |
||||
|
|
||||
|
final blackList = <Records>[].obs; |
||||
|
|
||||
|
|
||||
|
@override |
||||
|
void onInit() { |
||||
|
super.onInit(); |
||||
|
|
||||
|
listRefreshController = EasyRefreshController( |
||||
|
controlFinishRefresh: true, |
||||
|
controlFinishLoad: true, |
||||
|
); |
||||
|
_userApi = Get.find<UserApi>(); |
||||
|
getBlackList(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
getBlackList() async { |
||||
|
try{ |
||||
|
final response = await _userApi.userPageUserBlacklist( |
||||
|
pageNum: page.value, |
||||
|
pageSize: size.value, |
||||
|
); |
||||
|
if (response.data.isSuccess && response.data.data != null) { |
||||
|
final data = response.data.data?.records ?? []; |
||||
|
|
||||
|
blackList.addAll(data.toList()); |
||||
|
if((data.length ?? 0) == size.value){ |
||||
|
|
||||
|
listRefreshController.finishLoad(IndicatorResult.success); |
||||
|
} else { |
||||
|
listRefreshController.finishLoad(IndicatorResult.noMore); |
||||
|
} |
||||
|
} else { |
||||
|
|
||||
|
// 响应失败,抛出异常 |
||||
|
throw Exception(response.data.message ?? '获取数据失败'); |
||||
|
} |
||||
|
} catch(e) { |
||||
|
print('玫瑰记录获取失败: $e'); |
||||
|
SmartDialog.showToast('玫瑰记录获取失败'); |
||||
|
rethrow; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
unBlack(String blackUserId) async { |
||||
|
try { |
||||
|
final response = await _userApi.userDeleteUserBlacklist({ |
||||
|
"blackUserId": blackUserId, |
||||
|
}); |
||||
|
if (response.data.isSuccess) { |
||||
|
|
||||
|
SmartDialog.showToast('已解除拉黑'); |
||||
|
blackList.value = blackList.where((e) => e.blackUserId != blackUserId).toList(); |
||||
|
} else { |
||||
|
|
||||
|
// 响应失败,抛出异常 |
||||
|
throw Exception(response.data.message ?? '获取数据失败'); |
||||
|
} |
||||
|
} catch(e){ |
||||
|
print('解除拉黑失败: $e'); |
||||
|
SmartDialog.showToast('解除拉黑失败'); |
||||
|
rethrow; |
||||
|
|
||||
|
} finally { |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,72 @@ |
|||||
|
class BlacklistData { |
||||
|
List<Records>? records; |
||||
|
int? total; |
||||
|
int? size; |
||||
|
int? current; |
||||
|
int? pages; |
||||
|
|
||||
|
BlacklistData( |
||||
|
{this.records, this.total, this.size, this.current, this.pages}); |
||||
|
|
||||
|
BlacklistData.fromJson(Map<String, dynamic> json) { |
||||
|
if (json['records'] != null) { |
||||
|
records = <Records>[]; |
||||
|
json['records'].forEach((v) { |
||||
|
records!.add(new Records.fromJson(v)); |
||||
|
}); |
||||
|
} |
||||
|
total = json['total']; |
||||
|
size = json['size']; |
||||
|
current = json['current']; |
||||
|
pages = json['pages']; |
||||
|
} |
||||
|
|
||||
|
Map<String, dynamic> toJson() { |
||||
|
final Map<String, dynamic> data = new Map<String, dynamic>(); |
||||
|
if (this.records != null) { |
||||
|
data['records'] = this.records!.map((v) => v.toJson()).toList(); |
||||
|
} |
||||
|
data['total'] = this.total; |
||||
|
data['size'] = this.size; |
||||
|
data['current'] = this.current; |
||||
|
data['pages'] = this.pages; |
||||
|
return data; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
class Records { |
||||
|
String? id; |
||||
|
String? blackUserId; |
||||
|
String? miId; |
||||
|
String? nickName; |
||||
|
String? profilePhoto; |
||||
|
String? createTime; |
||||
|
|
||||
|
Records( |
||||
|
{this.id, |
||||
|
this.blackUserId, |
||||
|
this.miId, |
||||
|
this.nickName, |
||||
|
this.profilePhoto, |
||||
|
this.createTime}); |
||||
|
|
||||
|
Records.fromJson(Map<String, dynamic> json) { |
||||
|
id = json['id']; |
||||
|
blackUserId = json['blackUserId']; |
||||
|
miId = json['miId']; |
||||
|
nickName = json['nickName']; |
||||
|
profilePhoto = json['profilePhoto']; |
||||
|
createTime = json['createTime']; |
||||
|
} |
||||
|
|
||||
|
Map<String, dynamic> toJson() { |
||||
|
final Map<String, dynamic> data = new Map<String, dynamic>(); |
||||
|
data['id'] = this.id; |
||||
|
data['blackUserId'] = this.blackUserId; |
||||
|
data['miId'] = this.miId; |
||||
|
data['nickName'] = this.nickName; |
||||
|
data['profilePhoto'] = this.profilePhoto; |
||||
|
data['createTime'] = this.createTime; |
||||
|
return data; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,460 @@ |
|||||
|
import 'package:cached_network_image/cached_network_image.dart'; |
||||
|
import 'package:dating_touchme_app/components/page_appbar.dart'; |
||||
|
import 'package:dating_touchme_app/controller/mine/real_feedback_controller.dart'; |
||||
|
import 'package:dating_touchme_app/extension/ex_widget.dart'; |
||||
|
import 'package:dating_touchme_app/generated/assets.dart'; |
||||
|
import 'package:flutter/material.dart'; |
||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart'; |
||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; |
||||
|
import 'package:get/get.dart'; |
||||
|
import 'package:tdesign_flutter/tdesign_flutter.dart'; |
||||
|
|
||||
|
|
||||
|
class RealFeedbackPage extends StatelessWidget { |
||||
|
final int type; |
||||
|
const RealFeedbackPage({super.key, required this.type}); |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
void _showAvatarPopup(RealFeedbackController controller){ |
||||
|
Navigator.of(Get.context!).push( |
||||
|
TDSlidePopupRoute( |
||||
|
slideTransitionFrom: SlideTransitionFrom.bottom, |
||||
|
builder: (context) { |
||||
|
return Container( |
||||
|
height: 176, |
||||
|
decoration: BoxDecoration( |
||||
|
color: Colors.white, |
||||
|
borderRadius: BorderRadius.only( |
||||
|
topLeft: Radius.circular(12.0), |
||||
|
topRight: Radius.circular(12.0), |
||||
|
), |
||||
|
), |
||||
|
child: Column( |
||||
|
children: [ |
||||
|
ClipRRect( |
||||
|
borderRadius: BorderRadius.only( |
||||
|
topLeft: Radius.circular(12.0), |
||||
|
topRight: Radius.circular(12.0), |
||||
|
), |
||||
|
child: TDCell( |
||||
|
arrow: false, |
||||
|
titleWidget: Center( |
||||
|
child: Text('拍照', style: TextStyle(fontSize: 16.w, color: const Color.fromRGBO(51, 51, 51, 1))), |
||||
|
), |
||||
|
style: TDCellStyle( |
||||
|
padding: EdgeInsets.all(TDTheme.of(context).spacer16), |
||||
|
clickBackgroundColor: TDTheme.of(context).bgColorContainerHover, |
||||
|
cardBorderRadius: BorderRadius.only( |
||||
|
topLeft: Radius.circular(12.0), |
||||
|
topRight: Radius.circular(12.0), |
||||
|
) |
||||
|
), |
||||
|
onClick: (cell) async{ |
||||
|
Navigator.pop(context); |
||||
|
|
||||
|
if(9 - controller.imgList.length == 1){ |
||||
|
|
||||
|
await controller.handleCameraCapture(); |
||||
|
} else { |
||||
|
if(controller.imgList.length >= 9){ |
||||
|
|
||||
|
SmartDialog.showToast('超出数量限制,请先删除再尝试上传'); |
||||
|
return; |
||||
|
} |
||||
|
await controller.handleCameraCapture(); |
||||
|
} |
||||
|
}, |
||||
|
), |
||||
|
), |
||||
|
const TDDivider(), |
||||
|
TDCell( |
||||
|
arrow: false, |
||||
|
titleWidget: Center( |
||||
|
child: Text('从相册选择'), |
||||
|
), |
||||
|
onClick: (cell) async{ |
||||
|
Navigator.pop(context); |
||||
|
if(9 - controller.imgList.length == 1){ |
||||
|
await controller.handleGallerySelection(); |
||||
|
} else { |
||||
|
if(controller.imgList.length >= 9){ |
||||
|
|
||||
|
SmartDialog.showToast('超出数量限制,请先删除再尝试上传'); |
||||
|
return; |
||||
|
} |
||||
|
await controller.handleMultiGallerySelection(); |
||||
|
} |
||||
|
}, |
||||
|
), |
||||
|
Expanded( |
||||
|
child: Container( |
||||
|
color: Color(0xFFF3F3F3), |
||||
|
), |
||||
|
), |
||||
|
TDCell( |
||||
|
arrow: false, |
||||
|
titleWidget: Center( |
||||
|
child: Text('取消'), |
||||
|
), |
||||
|
onClick: (cell){ |
||||
|
Navigator.pop(context); |
||||
|
}, |
||||
|
), |
||||
|
], |
||||
|
), |
||||
|
); |
||||
|
}), |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
@override |
||||
|
Widget build(BuildContext context) { |
||||
|
return GetX<RealFeedbackController>( |
||||
|
init: RealFeedbackController(type: type), |
||||
|
builder: (controller){ |
||||
|
return Stack( |
||||
|
children: [ |
||||
|
Container( |
||||
|
width: 375.w, |
||||
|
height: 812.h, |
||||
|
color: Colors.white, |
||||
|
), |
||||
|
Positioned( |
||||
|
top: -320.w, |
||||
|
left: -270.w, |
||||
|
child: Image.asset( |
||||
|
Assets.imagesFeedbackBg, |
||||
|
width: 971.75.w, |
||||
|
height: 781.25.w, |
||||
|
fit: BoxFit.cover, |
||||
|
), |
||||
|
), |
||||
|
Positioned( |
||||
|
top: 53.w, |
||||
|
left: 207.w, |
||||
|
child: Image.asset( |
||||
|
Assets.imagesFeedbackIcon, |
||||
|
width: 140.w, |
||||
|
height: 140.w, |
||||
|
), |
||||
|
), |
||||
|
Scaffold( |
||||
|
appBar: PageAppbar(title: "", backgroundColor: Colors.transparent,), |
||||
|
backgroundColor: Colors.transparent, |
||||
|
body: Column( |
||||
|
crossAxisAlignment: CrossAxisAlignment.start, |
||||
|
children: [ |
||||
|
Container( |
||||
|
padding: EdgeInsets.symmetric( |
||||
|
vertical: 22.w, |
||||
|
horizontal: 14.w |
||||
|
), |
||||
|
child: Column( |
||||
|
crossAxisAlignment: CrossAxisAlignment.start, |
||||
|
children: [ |
||||
|
Text( |
||||
|
"意见反馈", |
||||
|
style: TextStyle( |
||||
|
fontSize: 21.w, |
||||
|
fontWeight: FontWeight.w500 |
||||
|
), |
||||
|
), |
||||
|
SizedBox(height: 5.w,), |
||||
|
Text( |
||||
|
"Hi,给出你的小建议吧~", |
||||
|
style: TextStyle( |
||||
|
fontSize: 13.w, |
||||
|
fontWeight: FontWeight.w500 |
||||
|
), |
||||
|
), |
||||
|
], |
||||
|
), |
||||
|
), |
||||
|
Expanded( |
||||
|
child: Container( |
||||
|
width: 375.w, |
||||
|
padding: EdgeInsets.symmetric( |
||||
|
vertical: 11.w, |
||||
|
horizontal: 14.w |
||||
|
), |
||||
|
decoration: BoxDecoration( |
||||
|
borderRadius: BorderRadius.vertical(top: Radius.circular(9.w)), |
||||
|
color: Colors.white |
||||
|
), |
||||
|
child: SingleChildScrollView( |
||||
|
child: Column( |
||||
|
crossAxisAlignment: CrossAxisAlignment.start, |
||||
|
children: [ |
||||
|
RichText( |
||||
|
text: TextSpan( |
||||
|
style: TextStyle( |
||||
|
fontSize: 16.w, |
||||
|
fontWeight: FontWeight.w500 |
||||
|
), |
||||
|
children: [ |
||||
|
TextSpan( |
||||
|
text: "选择问题的类型", |
||||
|
style: TextStyle( |
||||
|
color: const Color.fromRGBO(51, 51, 51, 1) |
||||
|
) |
||||
|
), |
||||
|
TextSpan( |
||||
|
text: "*", |
||||
|
style: TextStyle( |
||||
|
color: const Color.fromRGBO(248, 85, 66, 1) |
||||
|
) |
||||
|
) |
||||
|
] |
||||
|
), |
||||
|
), |
||||
|
SizedBox(height: 11.w,), |
||||
|
Wrap( |
||||
|
alignment: WrapAlignment.spaceBetween, |
||||
|
spacing: 10.w, |
||||
|
runSpacing: 10.w, |
||||
|
children: [ |
||||
|
...controller.tagList.asMap().entries.map((entry){ |
||||
|
return TagItem(item: entry.value, index: entry.key, active: controller.active.value, changeActive: controller.changeActive,); |
||||
|
}), |
||||
|
], |
||||
|
), |
||||
|
SizedBox(height: 20.w,), |
||||
|
RichText( |
||||
|
text: TextSpan( |
||||
|
style: TextStyle( |
||||
|
fontSize: 16.w, |
||||
|
fontWeight: FontWeight.w500 |
||||
|
), |
||||
|
children: [ |
||||
|
TextSpan( |
||||
|
text: "问题描述", |
||||
|
style: TextStyle( |
||||
|
color: const Color.fromRGBO(51, 51, 51, 1) |
||||
|
) |
||||
|
), |
||||
|
TextSpan( |
||||
|
text: "*", |
||||
|
style: TextStyle( |
||||
|
color: const Color.fromRGBO(248, 85, 66, 1) |
||||
|
) |
||||
|
) |
||||
|
] |
||||
|
), |
||||
|
), |
||||
|
SizedBox(height: 11.w,), |
||||
|
Container( |
||||
|
padding: EdgeInsets.all(14.w), |
||||
|
decoration: BoxDecoration( |
||||
|
borderRadius: BorderRadius.all(Radius.circular(8.w)), |
||||
|
color: const Color.fromRGBO(245, 245, 245, 1) |
||||
|
), |
||||
|
child: TextField( |
||||
|
controller: controller.messageController.value, |
||||
|
maxLength: 500, // 上限 |
||||
|
minLines: 5, // 多行 |
||||
|
maxLines: 5, // 自适应高度 |
||||
|
style: TextStyle( |
||||
|
fontSize: ScreenUtil().setWidth(13), |
||||
|
height: 1 |
||||
|
), |
||||
|
decoration: InputDecoration( |
||||
|
contentPadding: EdgeInsets.symmetric( |
||||
|
vertical: 0, |
||||
|
horizontal: 0 |
||||
|
), |
||||
|
hintText: "请输入交友心声", |
||||
|
|
||||
|
border: const OutlineInputBorder( |
||||
|
borderSide: BorderSide.none, // 这将移除边框 // 可选:设置圆角 |
||||
|
), |
||||
|
// 如果你希望聚焦时和未聚焦时都没有边框,也可以设置 focusedBorder 和 enabledBorder |
||||
|
focusedBorder: const OutlineInputBorder( |
||||
|
borderSide: BorderSide.none, |
||||
|
borderRadius: BorderRadius.all(Radius.circular(8.0)), |
||||
|
), |
||||
|
enabledBorder: const OutlineInputBorder( |
||||
|
borderSide: BorderSide.none, |
||||
|
borderRadius: BorderRadius.all(Radius.circular(8.0)), |
||||
|
), |
||||
|
), |
||||
|
onChanged: (value){ |
||||
|
controller.message.value = value; |
||||
|
}, |
||||
|
), |
||||
|
), |
||||
|
SizedBox(height: 10.w,), |
||||
|
Wrap( |
||||
|
spacing: 10.w, |
||||
|
runSpacing: 10.w, |
||||
|
children: [ |
||||
|
...controller.imgList.map((e){ |
||||
|
return Stack( |
||||
|
children: [ |
||||
|
CachedNetworkImage( |
||||
|
imageUrl: e, |
||||
|
width: 70.w, |
||||
|
height: 70.w, |
||||
|
fit: BoxFit.cover, |
||||
|
), |
||||
|
Positioned( |
||||
|
left: 5.w, |
||||
|
top: 5.w, |
||||
|
child: Container( |
||||
|
width: 20.w, |
||||
|
height: 20.w, |
||||
|
decoration: BoxDecoration( |
||||
|
borderRadius: BorderRadius.all(Radius.circular(20.w)), |
||||
|
color: const Color.fromRGBO(0, 0, 0, .3) |
||||
|
), |
||||
|
child: Icon( |
||||
|
Icons.close, |
||||
|
size: 20.w, |
||||
|
), |
||||
|
).onTap((){ |
||||
|
controller.imgList.remove(e); |
||||
|
|
||||
|
}), |
||||
|
) |
||||
|
], |
||||
|
); |
||||
|
}), |
||||
|
Container( |
||||
|
width: 70.w, |
||||
|
height: 70.w, |
||||
|
decoration: BoxDecoration( |
||||
|
borderRadius: BorderRadius.all(Radius.circular(8.w)), |
||||
|
border: Border.all(width: 1, color: const Color.fromRGBO(224, 224, 224, 1)) |
||||
|
), |
||||
|
child: Center( |
||||
|
child: Icon( |
||||
|
Icons.add, |
||||
|
size: 18.w, |
||||
|
color: const Color.fromRGBO(144, 144, 144, 1), |
||||
|
), |
||||
|
), |
||||
|
).onTap((){ |
||||
|
_showAvatarPopup(controller); |
||||
|
}) |
||||
|
], |
||||
|
), |
||||
|
SizedBox(height: 10.w,), |
||||
|
Text( |
||||
|
"上传问题截图可以让问题快速解决!", |
||||
|
style: TextStyle( |
||||
|
fontSize: 11.w, |
||||
|
color: const Color.fromRGBO(189, 189, 189, 1) |
||||
|
), |
||||
|
), |
||||
|
Container( |
||||
|
margin: EdgeInsets.symmetric(vertical: 30.w), |
||||
|
child: Container( |
||||
|
width: 350.w, |
||||
|
height: 45.w, |
||||
|
decoration: BoxDecoration( |
||||
|
borderRadius: BorderRadius.all(Radius.circular(45.w)), |
||||
|
gradient: LinearGradient( |
||||
|
begin: Alignment.centerLeft, // 90deg: 从左到右 |
||||
|
end: Alignment.centerRight, |
||||
|
colors: [ |
||||
|
Color.fromRGBO(131, 89, 255, 1), // 起点颜色 |
||||
|
Color.fromRGBO(77, 127, 231, 1), // 中间颜色 |
||||
|
Color.fromRGBO(61, 138, 224, 1), // 终点颜色 |
||||
|
], |
||||
|
stops: [0.0, 0.7753, 1.0], // 对应 0%、77.53%、100% |
||||
|
), |
||||
|
), |
||||
|
child: Center( |
||||
|
child: Text( |
||||
|
"确认", |
||||
|
style: TextStyle( |
||||
|
fontSize: 18.w, |
||||
|
color: Colors.white, |
||||
|
fontWeight: FontWeight.w500 |
||||
|
), |
||||
|
), |
||||
|
), |
||||
|
), |
||||
|
).onTap((){ |
||||
|
controller.sendFeedback(); |
||||
|
}) |
||||
|
], |
||||
|
), |
||||
|
), |
||||
|
), |
||||
|
) |
||||
|
], |
||||
|
), |
||||
|
) |
||||
|
], |
||||
|
); |
||||
|
}, |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
class TagItem extends StatefulWidget { |
||||
|
final Map item; |
||||
|
final int index; |
||||
|
final int active; |
||||
|
final void Function(int) changeActive; |
||||
|
const TagItem({super.key, required this.item, required this.index, required this.active, required this.changeActive}); |
||||
|
|
||||
|
@override |
||||
|
State<TagItem> createState() => _TagItemState(); |
||||
|
} |
||||
|
|
||||
|
class _TagItemState extends State<TagItem> { |
||||
|
@override |
||||
|
Widget build(BuildContext context) { |
||||
|
return Stack( |
||||
|
children: [ |
||||
|
Container( |
||||
|
padding: EdgeInsets.symmetric( |
||||
|
vertical: 9.w, |
||||
|
horizontal: 14.w |
||||
|
), |
||||
|
decoration: BoxDecoration( |
||||
|
borderRadius: BorderRadius.all(Radius.circular(8.w)), |
||||
|
border: Border.all(width: 1, color: widget.item["value"] == widget.active ? const Color.fromRGBO(117, 98, 249, 1) : const Color.fromRGBO(207, 207, 207, 1)), |
||||
|
color: widget.item["value"] == widget.active ? const Color.fromRGBO(194, 195, 255, 0.2) : Colors.transparent |
||||
|
), |
||||
|
child: Text( |
||||
|
widget.item["label"], |
||||
|
style: TextStyle( |
||||
|
fontSize: 13.w |
||||
|
), |
||||
|
), |
||||
|
).onTap((){ |
||||
|
widget.changeActive(widget.item["value"]); |
||||
|
}), |
||||
|
if(widget.active == widget.item["value"])Positioned( |
||||
|
bottom: 0, |
||||
|
right: 0, |
||||
|
child: Container( |
||||
|
width: 17.w, |
||||
|
height: 13.w, |
||||
|
decoration: BoxDecoration( |
||||
|
borderRadius: BorderRadius.only( |
||||
|
topLeft: Radius.circular(8.w), |
||||
|
bottomRight: Radius.circular(8.w) |
||||
|
), |
||||
|
color: const Color.fromRGBO(117, 98, 249, 1) |
||||
|
), |
||||
|
child: Center( |
||||
|
child: Image.asset( |
||||
|
Assets.imagesCheck, |
||||
|
width: 6.w, |
||||
|
), |
||||
|
), |
||||
|
), |
||||
|
) |
||||
|
], |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
Write
Preview
Loading…
Cancel
Save