Files
tuiche/lib/pages/mh_page/edit_address_page.dart
2025-07-23 13:55:46 +08:00

1202 lines
83 KiB
Dart

import 'package:ef/ef.dart';
import 'package:flutter/material.dart';
import 'package:flutter_city_picker/listener/picker_listener.dart';
import 'package:flutter_city_picker/model/address.dart';
import 'package:flutter_svg/svg.dart';
import 'package:flutterflow_ui/flutterflow_ui.dart';
import 'package:vbvs_app/common/color/appConstants.dart';
import 'package:vbvs_app/common/color/appFontsize.dart';
import 'package:vbvs_app/common/util/FitTool.dart';
import 'package:vbvs_app/common/util/MyUtils.dart';
import 'package:vbvs_app/component/tool/CustomCard.dart';
import 'package:vbvs_app/controller/mh_controller/address_controller.dart';
import 'package:vbvs_app/controller/mh_controller/address_list_controller.dart';
import 'package:vbvs_app/pages/mh_page/homepage/component/citypicker.dart';
class EditAddressPage extends GetView<AddressController>
implements CityPickerListener {
final scaffoldKey = GlobalKey<ScaffoldState>();
BoxConstraints? bodysize;
/// 0: 省
/// 1: 市
/// 2: 地区
/// 3: 街道
String _addressProvince = "请选择省";
String _addressCity = "请选择市";
String _addressArea = "请选择地区";
String _addressStreet = "请选择街道";
List<AddressNode> _selectProvince = [];
List<AddressNode> _selectCity = [];
List<AddressNode> _selectArea = [];
List<AddressNode> _selectStreet = [];
@override
Widget build(BuildContext context) {
AddressListController addressListController = Get.find();
var address =
Map<String, dynamic>.from(addressListController.model.address);
controller.model.default_ = address['default'];
controller.model.all_address = getAddressDesc(address);
controller.model.address = address['address'];
controller.model.name = address['name'];
controller.model.tel = address['tel'];
return LayoutBuilder(builder: (context, cc) {
bodysize = cc;
return GestureDetector(
// onTap: () => FocusScope.of(context).unfocus(),,
child: Container(
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/new_background.png'), // 本地图片
fit: BoxFit.fill, // 填满整个 Container
),
),
child: Scaffold(
// key: scaffoldKey,
backgroundColor: Colors.transparent,
appBar: AppBar(
backgroundColor: Colors.transparent,
automaticallyImplyLeading: false,
iconTheme: IconThemeData(color: Colors.white),
titleSpacing: 0,
title: Container(
width: double.infinity,
height: 180.rpx,
child: Stack(
alignment: Alignment.center,
children: [
// 中间居中的标题
Text(
'编辑地址',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontSize: 30.rpx,
),
),
// 左侧图标
Positioned(
left: 0.rpx,
child: returnIconButtomNew(),
),
],
),
),
actions: [],
centerTitle: false,
),
body: Container(
width: bodysize!.maxWidth,
height: bodysize!.maxHeight * 1,
child: Column(
// mainAxisSize: MainAxisSize.max,
children: [
// TitleComponentWidget(
// titleName: '编辑收货地址',
// ),
Expanded(
child: Padding(
padding:
EdgeInsetsDirectional.fromSTEB(0, 28.rpx, 0, 0),
child: Container(
width: MediaQuery.sizeOf(context).width,
height: MediaQuery.sizeOf(context).height * 0.907,
child: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 30.rpx),
child: Column(
mainAxisSize: MainAxisSize.max,
children: [
Container(
width: MediaQuery.sizeOf(context).width,
decoration: BoxDecoration(
color: Color(0xFF003058),
borderRadius:
BorderRadius.circular(16.rpx),
),
child: Align(
alignment:
const AlignmentDirectional(0, -1),
child: Container(
width: MediaQuery.sizeOf(context).width,
child: Padding(
padding: const EdgeInsetsDirectional
.fromSTEB(15, 0, 15, 0),
child: Container(
width: MediaQuery.sizeOf(context)
.width,
// decoration: BoxDecoration(
// color: Color(0XFF003058),
// ),
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.max,
children: [
Container(
width: MediaQuery.sizeOf(
context)
.width,
height: MediaQuery.sizeOf(
context)
.height *
0.038,
constraints:
const BoxConstraints(
minHeight: 46,
),
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(
16),
),
child: Row(
mainAxisSize:
MainAxisSize.max,
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: [
Expanded(
child: Container(
width: MediaQuery
.sizeOf(
context)
.width *
0.1,
height: 100,
decoration:
BoxDecoration(),
child: Align(
alignment:
const AlignmentDirectional(
-1, 0),
child: Text(
'地址信息',
style:
TextStyle(
fontFamily:
'Readex Pro',
color: Colors
.white,
fontSize:
AppFontsize
.title_size,
letterSpacing:
0,
fontWeight:
FontWeight
.w600,
),
),
),
),
),
Container(
height:
MediaQuery.sizeOf(
context)
.height *
0.038,
constraints:
BoxConstraints(
minWidth: 104.rpx,
),
decoration:
BoxDecoration(),
child: Row(
mainAxisSize:
MainAxisSize
.max,
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: [
Align(
alignment:
const AlignmentDirectional(
0, 0),
child: Padding(
padding:
const EdgeInsetsDirectional
.fromSTEB(
0,
3,
0,
0),
child:
Container(
width:
60.rpx,
height:
60.rpx,
decoration:
const BoxDecoration(),
child:
Align(
alignment:
const AlignmentDirectional(
0,
0),
child:
Theme(
data:
ThemeData(
checkboxTheme:
CheckboxThemeData(
visualDensity:
VisualDensity.compact,
materialTapTargetSize:
MaterialTapTargetSize.shrinkWrap,
shape:
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(64),
),
),
unselectedWidgetColor:
const Color(0xFFD3D3D3),
),
child: Obx(
() {
return Checkbox(
value:
controller.model.default_ == 1,
onChanged:
(newValue) {
controller.model.default_ = (newValue ?? false) ? 1 : 0;
controller.updateAll();
},
side:
const BorderSide(
width: 1.5,
color: Colors.white,
),
activeColor:
const Color(0xFF84F5FF),
checkColor:
const Color(0xFF011D33),
// Add this to prevent double triggers
materialTapTargetSize:
MaterialTapTargetSize.shrinkWrap,
);
}),
),
),
),
),
),
Obx(
() => Text(
'默认',
style:
TextStyle(
fontFamily:
'Readex Pro',
fontSize:
26.rpx,
letterSpacing:
0,
color: controller.model.default_ ==
1
? const Color(
0XFF84F5FF)
: Colors
.white,
),
),
)
],
),
),
].divide(const SizedBox(
width: 12)),
),
),
Container(
// width: MediaQuery.sizeOf(
// context)
// .width,
height:
bodysize!.maxHeight *
0.038,
constraints: BoxConstraints(
minHeight: 61.rpx,
),
decoration: BoxDecoration(),
child: Row(
mainAxisSize:
MainAxisSize.max,
children: [
Container(
width:
MediaQuery.sizeOf(
context)
.width *
0.17,
decoration:
BoxDecoration(),
constraints:
BoxConstraints(
minWidth:
105.rpx,
maxWidth:
105.rpx),
child: Row(
mainAxisSize:
MainAxisSize
.max,
children: [
Text(
'收件人',
style: TextStyle(
fontFamily:
'Readex Pro',
fontSize:
AppFontsize
.normal_text_size,
letterSpacing:
0,
color: Colors
.white),
),
],
),
),
Expanded(
child: Container(
decoration:
BoxDecoration(
color:
Colors.white,
borderRadius:
BorderRadius
.circular(
8),
),
width:
double.infinity,
child: Padding(
padding:
EdgeInsetsDirectional
.fromSTEB(
35.rpx,
0,
35.rpx,
0),
child: Row(
mainAxisSize:
MainAxisSize
.max,
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: [
Expanded(
child:
Container(
child:
Align(
alignment: AlignmentDirectional(
-1,
0),
child:
TextFormField(
initialValue:
address['name'],
onChanged:
(value) {
controller.model.name =
value;
},
autofocus:
false,
obscureText:
false,
decoration:
InputDecoration(
contentPadding:
EdgeInsets.all(0),
isDense:
true,
labelStyle:
TextStyle(
fontFamily: 'Inter',
fontSize: 26.rpx,
letterSpacing: 0.0,
),
hintStyle:
TextStyle(
fontFamily: 'Inter',
fontSize: 26.rpx,
letterSpacing: 0.0,
color: themeController.currentColor.sc4,
),
enabledBorder:
OutlineInputBorder(
borderSide: BorderSide(
color: Color(0x00000000),
width: 1.rpx,
),
borderRadius: BorderRadius.circular(8.rpx),
),
focusedBorder:
OutlineInputBorder(
borderSide: BorderSide(
color: Color(0x00000000),
width: 1.rpx,
),
borderRadius: BorderRadius.circular(8.rpx),
),
errorBorder:
OutlineInputBorder(
borderSide: BorderSide(
color: Colors.red,
width: 1.rpx,
),
borderRadius: BorderRadius.circular(8.rpx),
),
focusedErrorBorder:
OutlineInputBorder(
borderSide: BorderSide(
color: Colors.red,
width: 1.rpx,
),
borderRadius: BorderRadius.circular(8.rpx),
),
filled:
false,
fillColor:
Colors.white,
),
style:
TextStyle(
fontFamily:
'Readex Pro',
letterSpacing:
0,
color:
Colors.black,
fontSize:
26.rpx,
),
// cursorColor:
// Colors.black,
// validator: _model
// .textControllerValidator
// .asValidator(context),
),
),
),
),
],
),
),
),
),
].divide(const SizedBox(
width: 15)),
),
),
Container(
// width: MediaQuery.sizeOf(
// context)
// .width,
height:
bodysize!.maxHeight *
0.038,
constraints: BoxConstraints(
minHeight: 61.rpx,
),
decoration: BoxDecoration(),
child: Row(
mainAxisSize:
MainAxisSize.max,
children: [
Container(
width:
MediaQuery.sizeOf(
context)
.width *
0.17,
decoration:
BoxDecoration(),
constraints:
BoxConstraints(
minWidth:
105.rpx,
maxWidth:
105.rpx),
child: Row(
mainAxisSize:
MainAxisSize
.max,
children: [
Text(
'手机号',
style: TextStyle(
fontFamily:
'Readex Pro',
fontSize:
AppFontsize
.normal_text_size,
letterSpacing:
0,
color: Colors
.white),
),
],
),
),
Expanded(
child: Container(
decoration:
BoxDecoration(
color:
Colors.white,
borderRadius:
BorderRadius
.circular(
8),
),
width:
double.infinity,
child: Padding(
padding:
EdgeInsetsDirectional
.fromSTEB(
35.rpx,
0,
35.rpx,
0),
child: Row(
mainAxisSize:
MainAxisSize
.max,
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: [
Expanded(
child:
Container(
child:
Align(
alignment: AlignmentDirectional(
-1,
0),
child:
TextFormField(
initialValue:
address['tel'],
onChanged:
(value) {
controller.model.tel =
value;
},
autofocus:
false,
obscureText:
false,
decoration:
InputDecoration(
contentPadding:
EdgeInsets.all(0),
isDense:
true,
labelStyle:
TextStyle(
fontFamily: 'Inter',
fontSize: 26.rpx,
letterSpacing: 0.0,
),
hintStyle:
TextStyle(
fontFamily: 'Inter',
fontSize: 26.rpx,
letterSpacing: 0.0,
color: themeController.currentColor.sc4,
),
enabledBorder:
OutlineInputBorder(
borderSide: BorderSide(
color: Color(0x00000000),
width: 1.rpx,
),
borderRadius: BorderRadius.circular(8.rpx),
),
focusedBorder:
OutlineInputBorder(
borderSide: BorderSide(
color: Color(0x00000000),
width: 1.rpx,
),
borderRadius: BorderRadius.circular(8.rpx),
),
errorBorder:
OutlineInputBorder(
borderSide: BorderSide(
color: Colors.red,
width: 1.rpx,
),
borderRadius: BorderRadius.circular(8.rpx),
),
focusedErrorBorder:
OutlineInputBorder(
borderSide: BorderSide(
color: Colors.red,
width: 1.rpx,
),
borderRadius: BorderRadius.circular(8.rpx),
),
filled:
false,
fillColor:
Colors.white,
),
style:
TextStyle(
fontFamily:
'Readex Pro',
letterSpacing:
0,
color:
Colors.black,
fontSize:
26.rpx,
),
// cursorColor:
// Colors.black,
// validator: _model
// .textControllerValidator
// .asValidator(context),
),
),
),
),
],
),
),
),
),
].divide(const SizedBox(
width: 15)),
),
),
Container(
width: MediaQuery.sizeOf(
context)
.width,
height: MediaQuery.sizeOf(
context)
.height *
0.038,
constraints:
const BoxConstraints(
minHeight: 31,
),
child: Row(
mainAxisSize:
MainAxisSize.max,
children: [
Container(
width:
MediaQuery.sizeOf(
context)
.width *
0.17,
height:
MediaQuery.sizeOf(
context)
.height *
0.038,
constraints:
BoxConstraints(
minWidth:
105.rpx,
maxWidth:
105.rpx),
child: Row(
mainAxisSize:
MainAxisSize
.max,
children: [
InkWell(
onTap: () {
// CityPicker
// .show(
// context:
// context,
// cityPickerListener:
// this,
// );
},
child: Text(
'所在地区',
style:
TextStyle(
fontFamily:
'Readex Pro',
fontSize:
AppFontsize
.normal_text_size,
letterSpacing:
0,
color: Colors
.white,
),
),
),
],
),
),
Expanded(
child: InkWell(
onTap: () {
CityPicker.show(
context:
context,
cityPickerListener:
this,
);
},
child: Container(
width: 100,
height: 100,
decoration:
BoxDecoration(
color: const Color(
0xFFF3F5F6),
borderRadius:
BorderRadius
.circular(
8),
),
alignment:
Alignment
.center,
child: Obx(() {
return Row(
children: [
Expanded(
child:
Padding(
padding: EdgeInsets.only(
left: 27
.rpx,
right:
10.rpx),
child:
Text(
controller.model.all_address ??
'',
maxLines:
1,
overflow:
TextOverflow.ellipsis,
style:
TextStyle(
fontFamily:
'Readex Pro',
letterSpacing:
0,
color:
Color(0xFF333333),
fontSize:
26.rpx,
),
),
)),
Padding(
padding:
EdgeInsets.only(right: 27.rpx),
child: Container(
height: 30.rpx,
width: 30.rpx,
child: SvgPicture.asset(
'assets/img/icon/expand_more.svg',
color: Colors.black,
)))
],
);
}),
),
),
)
].divide(const SizedBox(
width: 15)),
),
),
Padding(
padding:
const EdgeInsetsDirectional
.fromSTEB(
0, 0, 0, 15),
child: Container(
width: MediaQuery.sizeOf(
context)
.width,
height: MediaQuery.sizeOf(
context)
.height *
0.093,
constraints:
const BoxConstraints(
minHeight: 76,
),
child: Row(
mainAxisSize:
MainAxisSize.max,
children: [
Align(
alignment:
const AlignmentDirectional(
-1, -1),
child: Container(
width: MediaQuery
.sizeOf(
context)
.width *
0.17,
height: MediaQuery
.sizeOf(
context)
.height *
0.038,
constraints:
BoxConstraints(
minWidth: 105
.rpx,
maxWidth:
105.rpx),
child: Row(
mainAxisSize:
MainAxisSize
.max,
children: [
Text(
'详细地址',
style: TextStyle(
fontFamily:
'Readex Pro',
fontSize:
AppFontsize
.normal_text_size,
letterSpacing:
0,
color: Colors
.white),
),
// Align(
// alignment:
// AlignmentDirectional(
// 0,
// -0.1),
// child: Icon(
// Icons
// .star_rate_sharp,
// color: Color(
// 0xFFE50012),
// size: 8,
// ),
// ),
],
),
),
),
Expanded(
child: Container(
width: 100,
height: MediaQuery
.sizeOf(
context)
.height *
0.093,
decoration:
BoxDecoration(
color: const Color(
0xFFF3F5F6),
borderRadius:
BorderRadius
.circular(
8),
),
child:
TextFormField(
// autofocus: true,
onChanged:
(val) {
controller
.model
.address =
val;
},
initialValue:
address[
'address'],
maxLines: 2,
obscureText:
false,
decoration:
InputDecoration(
contentPadding:
EdgeInsets
.symmetric(
vertical:
10.rpx,
horizontal:
35.rpx,
),
labelStyle:
TextStyle(
fontFamily:
'Readex Pro',
letterSpacing:
0,
),
hintStyle:
TextStyle(
fontFamily:
'Readex Pro',
letterSpacing:
0,
),
enabledBorder:
UnderlineInputBorder(
borderSide:
const BorderSide(
color: Color(
0x00000000),
width: 2,
),
borderRadius:
BorderRadius
.circular(8),
),
focusedBorder:
UnderlineInputBorder(
borderSide:
const BorderSide(
color: Color(
0x00000000),
width: 2,
),
borderRadius:
BorderRadius
.circular(8),
),
errorBorder:
UnderlineInputBorder(
borderSide:
const BorderSide(
color: Color(
0x00000000),
width: 2,
),
borderRadius:
BorderRadius
.circular(8),
),
focusedErrorBorder:
UnderlineInputBorder(
borderSide:
const BorderSide(
color: Color(
0x00000000),
width: 2,
),
borderRadius:
BorderRadius
.circular(8),
),
),
style:
TextStyle(
fontFamily:
'Readex Pro',
letterSpacing:
0,
color: Colors
.black,
fontSize:
26.rpx,
),
// cursorColor:
// Colors
// .black,
),
),
),
].divide(const SizedBox(
width: 15)),
),
),
),
].divide(
const SizedBox(height: 15)),
),
),
),
),
),
),
),
],
),
)),
),
),
),
Padding(
padding: const EdgeInsetsDirectional.fromSTEB(15, 0,
15, AppConstants.page_button_bottom_padding),
child: CustomCard(
borderRadius: 16.rpx,
gradientDirection: GradientDirection.vertical,
onTap: () async {
if (controller.model.all_address == null ||
controller.model.all_address!.isEmpty) {
showToast("地址不能为空");
return;
}
if (controller.model.name == null ||
controller.model.name!.isEmpty) {
showToast("名字不能为空");
return;
}
if (controller.model.address == null ||
controller.model.address!.isEmpty) {
showToast("详细地址不能为空");
return;
}
if (controller.model.tel == null ||
controller.model.tel!.isEmpty) {
showToast("手机号不能为空");
return;
}
if (!MyUtils.isValidPhoneNumber(
controller.model.tel!)) {
showToast("无效的手机号码");
return;
}
if (addressListController.model.type == 1) {
await controller.addAddress(
controller.model, context);
} else {
await controller.updateAddress(
address, controller.model);
}
await addressListController.getAddressList();
Get.back();
controller.model = AddressModel();
controller.updateAll();
},
colors: const [
Color(0xFFFCFCFC),
Color(0xFFF8FAF9),
Color(0XFFECF6F3),
Color(0XFFD9F0E9),
Color(0xFFCEECE3)
],
child: Container(
width: double.infinity,
height: 90.rpx,
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6),
),
child: Text(
"保存",
style: TextStyle(
fontFamily: 'Readex Pro',
color: stringToColor("#011D33"),
letterSpacing: 0,
fontSize: 30.rpx,
),
),
),
)),
],
),
),
)));
});
}
@override
Future<List<AddressNode>> onDataLoad(
int index, String code, String name) async {
debugPrint("onDataLoad ---> index=$index, code=$code, name=$name");
return HttpUtils.getCityData(code, index);
}
@override
void onFinish(List<AddressNode> data) {
debugPrint("onFinish");
String add = "";
for (var node in data) {
add += "${node.name} ";
}
if (controller.model.currentType == 0) {
_addressProvince = add;
_selectProvince = data;
} else if (controller.model.currentType == 1) {
_addressCity = add;
_selectCity = data;
} else if (controller.model.currentType == 2) {
_addressArea = add;
_selectArea = data;
} else {
_addressStreet = add;
_selectStreet = data;
}
controller.model.all_address = add;
controller.model.addressList = data;
controller.updateAll();
}
String getAddressDesc(Map<String, dynamic> address) {
// 提取地址的各个字段
String? province = address['province'];
String? city = address['city'];
String? area = address['county'];
String? street = address['street'];
// 使用 where 过滤掉 null 或空字符串的值,并用空格连接有效的字段
return [province, city, area, street]
.where((element) => element != null && element.isNotEmpty)
.join(' ');
}
}
// class HttpUtils {
// static Future<List<AddressNode>> getCityData(String code, int index) async {
// final AddressController addressController = Get.find<AddressController>();
// addressController.model.currentType = 1;
// if (code.isEmpty) {
// addressController.updateAll();
// return addressController.getData();
// }
// addressController.model.currentType = index + 1;
// //控制选择区域层级 1.省 2.市 3.区 4.街道
// if (addressController.model.currentType > 3) {
// return [];
// }
// return addressController.getData();
// }
// }
class HttpUtils {
static Future<List<AddressNode>> getCityData(String code, int index) async {
final AddressController controller = Get.find<AddressController>();
controller.model.currentType = index;
return controller.getData(parentCode: code, level: index);
}
}