feat: inventory inout feature finsihed

This commit is contained in:
louis 2024-03-26 11:35:39 +08:00
parent 7986fde5d3
commit fc0187acf0
21 changed files with 951 additions and 314 deletions

View File

@ -36,7 +36,23 @@ Future<Response<PaginationData>> getProducts(Map params) {
// //
Future<Response<PaginationData>> getInventoryInout(Map params) { Future<Response<PaginationData>> getInventoryInout(Map params) {
return DioService.dio.get<PaginationData>(Urls.getInventoryInout, return DioService.dio.get<PaginationData>(Urls.inventoryInout,
queryParameters: {'page': 1, 'pageSize': 10, ...(params)});
}
//
Future<Response> createInventoryInout(Map params) {
return DioService.dio.post(Urls.inventoryInout, data: params);
}
//
Future<Response> updateInventoryInout(int id, Map params) {
return DioService.dio.put('${Urls.inventoryInout}/$id', data: params);
}
//
Future<Response<PaginationData>> getInventory(Map params) {
return DioService.dio.get<PaginationData>(Urls.inventory,
queryParameters: {'page': 1, 'pageSize': 10, ...(params)}); queryParameters: {'page': 1, 'pageSize': 10, ...(params)});
} }
@ -66,27 +82,16 @@ Future<Response> saveUserInfo(Map<String, dynamic> data) {
return DioService.dio.post(Urls.saveUserInfo, data: data); return DioService.dio.post(Urls.saveUserInfo, data: data);
} }
Future<Response> uploadImg(File file) async { Future<Response> uploadImg(File file,
// DateTime dateTime = DateTime.now(); {String? bussinessModule, String? bussinessRecordId}) async {
// String fileName = file.path.split('/').last; String fileName = file.path.split('/').last;
// String format = fileName.split('.').last; final filePath = await MultipartFile.fromFile(file.path, filename: fileName);
// int imageTimeName = dateTime.millisecondsSinceEpoch + final formData = FormData.fromMap({
// (dateTime.microsecondsSinceEpoch ~/ 1000000); 'bussinessRecordId': bussinessRecordId,
// String imageName = '$imageTimeName.$format'; 'bussinessModule': bussinessModule,
// String host = AppInfoService.to.ossPolicy.host!; 'file': filePath,
// String dir = AppInfoService.to.ossPolicy.dir!; });
// final filePath = await MultipartFile.fromFile(file.path, filename: fileName); return DioService.dio.post(Urls.uploadAttachemnt, data: formData);
// final formData = FormData.fromMap({
// 'ossaccessKeyId': AppInfoService.to.ossPolicy.accessKeyId,
// 'policy': AppInfoService.to.ossPolicy.policy,
// 'signature': AppInfoService.to.ossPolicy.signature,
// 'callback': AppInfoService.to.ossPolicy.callback,
// 'key': '$dir/$imageName',
// 'file': filePath,
// });
// return DioService.dio.post(host, data: formData);
return {} as Response;
} }
Future<Response> updateAvatar(String filename) { Future<Response> updateAvatar(String filename) {

View File

@ -4,10 +4,15 @@ enum BackActionEnum {
enum LoginEnum { apple, fastLogin } enum LoginEnum { apple, fastLogin }
enum InventoryInOrOutEnum { In, Out } class InventoryInOrOutEnum {
static const int In = 0;
static const int Out = 1;
}
// InventoryInOrOutEnum class StorageBussinessModuleEnum {
const Map<InventoryInOrOutEnum, int> InventoryInOrOutEnumValues = { static const Contract = 'contract';
InventoryInOrOutEnum.In: 0, static const Company = 'company';
InventoryInOrOutEnum.Out: 1, static const MaterialsInOut = 'materialsInOut';
}; static const Product = 'product';
static const Project = 'project';
}

View File

@ -8,7 +8,9 @@ class Urls {
static String getUserInfo = 'account/profile'; static String getUserInfo = 'account/profile';
static String getProjects = 'project'; static String getProjects = 'project';
static String getProducts = 'product'; static String getProducts = 'product';
static String getInventoryInout = 'materials-in-out'; static String inventoryInout = 'materials-in-out';
static String inventory = 'materials-inventory';
static String updateAvatar = 'user/updateAvatar'; static String updateAvatar = 'user/updateAvatar';
static String getDictType = 'system/dict-type/all'; static String getDictType = 'system/dict-type/all';
static String uploadAttachemnt = 'tools/upload';
} }

View File

@ -4,4 +4,5 @@ class TextEnum {
static const String backToTodayButtonText = '今天'; static const String backToTodayButtonText = '今天';
static const String createInventoryInOutBtnText = '提交'; static const String createInventoryInOutBtnText = '提交';
static const String inventoryInOutDialogTitle = '出入库登记'; static const String inventoryInOutDialogTitle = '出入库登记';
static const String serverErrorMsg = ' 服务器繁忙,请重试或者联系管理员';
} }

View File

@ -0,0 +1,72 @@
import 'package:sk_base_mobile/models/product.model.dart';
import 'package:sk_base_mobile/models/project.model.dart';
class InventoryModel {
InventoryModel({
required this.id,
required this.createdAt,
required this.updatedAt,
required this.projectId,
required this.productId,
this.position,
required this.quantity,
required this.unitPrice,
this.remark,
required this.isDelete,
required this.inventoryNumber,
required this.project,
required this.product,
});
final int? id;
final DateTime? createdAt;
final DateTime? updatedAt;
final int? projectId;
final int? productId;
final String? position;
final int? quantity;
final String? unitPrice;
final dynamic remark;
final int? isDelete;
final String? inventoryNumber;
final ProjectModel? project;
final ProductModel? product;
factory InventoryModel.fromJson(Map<String, dynamic> json) {
return InventoryModel(
id: json["id"],
createdAt: DateTime.tryParse(json["createdAt"] ?? ""),
updatedAt: DateTime.tryParse(json["updatedAt"] ?? ""),
projectId: json["projectId"],
productId: json["productId"],
position: json["position"],
quantity: json["quantity"],
unitPrice: json["unitPrice"],
remark: json["remark"],
isDelete: json["isDelete"],
inventoryNumber: json["inventoryNumber"],
project: json["project"] == null
? null
: ProjectModel.fromJson(json["project"]),
product: json["product"] == null
? null
: ProductModel.fromJson(json["product"]),
);
}
Map<String, dynamic> toJson() => {
"id": id,
"createdAt": createdAt?.toIso8601String(),
"updatedAt": updatedAt?.toIso8601String(),
"projectId": projectId,
"productId": productId,
"position": position,
"quantity": quantity,
"unitPrice": unitPrice,
"remark": remark,
"isDelete": isDelete,
"inventoryNumber": inventoryNumber,
"project": project?.toJson(),
"product": product?.toJson(),
};
}

View File

@ -0,0 +1,21 @@
class UploadResultModel {
UploadResultModel({
required this.path,
required this.id,
});
final String? path;
final int? id;
factory UploadResultModel.fromJson(Map<String, dynamic> json) {
return UploadResultModel(
path: json["path"],
id: json["id"],
);
}
Map<String, dynamic> toJson() => {
"path": path,
"id": id,
};
}

View File

@ -232,12 +232,12 @@ class InventoryInoutCard extends StatelessWidget {
} }
Widget buildInOrOut() { Widget buildInOrOut() {
final bgColor = controller.list[ind][index].inOrOut == final bgColor =
InventoryInOrOutEnumValues[InventoryInOrOutEnum.In] controller.list[ind][index].inOrOut == InventoryInOrOutEnum.In
? AppTheme.snackbarSuccessBackgroudColor ? AppTheme.snackbarSuccessBackgroudColor
: AppTheme.snackbarErrorBackgroudColor; : AppTheme.snackbarErrorBackgroudColor;
final textInOut = controller.list[ind][index].inOrOut == final textInOut =
InventoryInOrOutEnumValues[InventoryInOrOutEnum.In] controller.list[ind][index].inOrOut == InventoryInOrOutEnum.In
? '' ? ''
: ''; : '';
return Container( return Container(

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:sk_base_mobile/app_theme.dart'; import 'package:sk_base_mobile/app_theme.dart';
import 'package:sk_base_mobile/constants/enum.dart';
import 'package:sk_base_mobile/db_helper/dbHelper.dart'; import 'package:sk_base_mobile/db_helper/dbHelper.dart';
import 'package:sk_base_mobile/screens/new_inventory_inout/new_inventory_inout.dart'; import 'package:sk_base_mobile/screens/new_inventory_inout/new_inventory_inout.dart';
import 'package:sk_base_mobile/util/date.util.dart'; import 'package:sk_base_mobile/util/date.util.dart';
@ -33,14 +34,12 @@ class InventoryInoutController extends GetxController {
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();
getTasks(); getData();
// getInoutHistory();
} }
Future<void> changePage(int index) async { Future<void> changePage(int index) async {
LoggerUtil() LoggerUtil()
.info(DateUtil.format(DateTime.now().add(Duration(days: -index)))); .info(DateUtil.format(DateTime.now().add(Duration(days: -index))));
} }
/// ///
@ -80,7 +79,8 @@ class InventoryInoutController extends GetxController {
child: ElevatedButton( child: ElevatedButton(
onPressed: () { onPressed: () {
Get.back(); Get.back();
showInventoryInoutCreateDialog('in'); showInventoryInoutCreateDialog(
InventoryInOrOutEnum.In);
}, },
style: ButtonStyle( style: ButtonStyle(
backgroundColor: MaterialStateProperty.all( backgroundColor: MaterialStateProperty.all(
@ -147,7 +147,8 @@ class InventoryInoutController extends GetxController {
child: ElevatedButton( child: ElevatedButton(
onPressed: () { onPressed: () {
Get.back(); Get.back();
showInventoryInoutCreateDialog('out'); showInventoryInoutCreateDialog(
InventoryInOrOutEnum.Out);
}, },
style: ButtonStyle( style: ButtonStyle(
backgroundColor: MaterialStateProperty.all( backgroundColor: MaterialStateProperty.all(
@ -204,15 +205,17 @@ class InventoryInoutController extends GetxController {
} }
/// ///
Future<void> showInventoryInoutCreateDialog(String inOrOut) async { Future showInventoryInoutCreateDialog(int inOrOut) async {
final isTablet = Responsive.isTablet(Get.context!); final isTablet = Responsive.isTablet(Get.context!);
isTablet return isTablet
? Get.generalDialog( ? Get.generalDialog(
barrierLabel: "CreateInventoryInout", barrierLabel: "CreateInventoryInout",
barrierDismissible: true, barrierDismissible: true,
transitionDuration: const Duration(milliseconds: 400), transitionDuration: const Duration(milliseconds: 400),
pageBuilder: (_, __, ___) { pageBuilder: (_, __, ___) {
return NewInventoryInout(); return NewInventoryInout(
inOrOut: inOrOut,
);
}, },
transitionBuilder: (_, anim, __, child) { transitionBuilder: (_, anim, __, child) {
Tween<Offset> tween; Tween<Offset> tween;
@ -224,7 +227,11 @@ class InventoryInoutController extends GetxController {
child: child, child: child,
); );
}, },
) ).then((value) {
if (value != null) {
getData();
}
})
: showModalBottomSheet( : showModalBottomSheet(
elevation: 0, elevation: 0,
isScrollControlled: true, isScrollControlled: true,
@ -250,7 +257,7 @@ class InventoryInoutController extends GetxController {
// return queryResult.map((e) => TaskModel.fromMap(e)).toList(); // return queryResult.map((e) => TaskModel.fromMap(e)).toList();
} }
getTasks() async { getData() async {
getInoutHistory().then((value) { getInoutHistory().then((value) {
model.value = value; model.value = value;
getSepretLists(); getSepretLists();

View File

@ -0,0 +1,408 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
import 'package:sk_base_mobile/app_theme.dart';
import 'package:sk_base_mobile/constants/bg_color.dart';
import 'package:sk_base_mobile/models/index.dart';
import 'package:sk_base_mobile/models/inventory.model.dart';
import 'package:sk_base_mobile/util/debouncer.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
import 'package:sk_base_mobile/apis/api.dart' as Api;
class InventorySearch extends StatelessWidget {
Function(InventoryModel)? onInventorySelected;
Function(InventoryModel)? beforeSelectedCheck;
InventorySearch(
{super.key, this.onInventorySelected, this.beforeSelectedCheck});
final controller = Get.put(InventorySearchController());
final listTitleTextStyle =
TextStyle(fontSize: ScreenAdaper.sp(20), fontWeight: FontWeight.w600);
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.symmetric(
horizontal: ScreenAdaper.width(20),
vertical: ScreenAdaper.height(20)),
child: Column(
children: [
buildSearchBar(),
SizedBox(
height: ScreenAdaper.height(defaultPadding) / 2,
),
Expanded(child: buildInventoryList())
],
),
);
}
Widget buildSearchBar() {
final doSearch = debouncer((String value) {
controller.searchKey.value = value;
controller.onRefresh();
}, delayTime: 500);
return Row(
children: [
Expanded(
flex: 5,
child: TextField(
controller: controller.searchBarTextConroller,
onChanged: (value) => doSearch(value),
decoration: InputDecoration(
contentPadding: EdgeInsets.symmetric(
vertical: ScreenAdaper.height(10),
horizontal: ScreenAdaper.width(10)),
hintText: '名称,编号,规格',
floatingLabelBehavior: FloatingLabelBehavior.always,
prefixIcon: const Icon(Icons.search),
// searchBarController有值时不显示
suffixIcon: Obx(() => controller.searchKey.value.isEmpty
? const SizedBox()
: IconButton(
icon: const Icon(Icons.clear),
onPressed: () {
controller.searchKey.value = '';
controller.searchBarTextConroller.clear();
doSearch('');
},
)),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
))),
SizedBox(
width: ScreenAdaper.width(20),
),
buildHasInventoryPicker()
],
);
}
Widget buildHasInventoryPicker() {
return Container(
width: ScreenAdaper.width(150),
constraints: BoxConstraints(minWidth: ScreenAdaper.width(150)),
child: Obx(
() => DropdownButtonFormField(
value: controller.hasInventoryStatus.value,
items: [
DropdownMenuItem(
child: Text('全部'),
value: 0,
),
DropdownMenuItem(
child: const Text('有库存'),
value: 1,
),
DropdownMenuItem(
child: const Text('无库存'),
value: 2,
),
],
onChanged: (value) {
controller.hasInventoryStatus.value = value!;
controller.onRefresh();
},
decoration: InputDecoration(
contentPadding: EdgeInsets.symmetric(
vertical: ScreenAdaper.height(15),
horizontal: ScreenAdaper.width(15)),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(ScreenAdaper.sp(15)),
),
)),
));
}
Widget buildInventoryList() {
final textStyle = TextStyle(fontSize: ScreenAdaper.sp(22));
return Obx(() => SmartRefresher(
enablePullDown: true,
enablePullUp: true,
controller: controller.refreshController,
onLoading: controller.onLoading,
onRefresh: controller.onRefresh,
child: Table(columnWidths: {
0: FixedColumnWidth(100),
1: FlexColumnWidth(2),
2: MinColumnWidth(FixedColumnWidth(200), FixedColumnWidth(200)),
3: MinColumnWidth(FixedColumnWidth(100), FixedColumnWidth(100)),
4: MinColumnWidth(FixedColumnWidth(80), FixedColumnWidth(80)),
}, children: [
// table header
TableRow(
decoration: BoxDecoration(
border:
Border(bottom: BorderSide(color: AppTheme.dividerColor))),
children: [
TableCell(
verticalAlignment: TableCellVerticalAlignment.middle,
child: Container(
alignment: Alignment.centerLeft,
height: ScreenAdaper.height(60),
child: Text(
'库存编号',
style: listTitleTextStyle,
),
)),
TableCell(
verticalAlignment: TableCellVerticalAlignment.middle,
child: Text(
'产品名称',
style: listTitleTextStyle,
)),
TableCell(
verticalAlignment: TableCellVerticalAlignment.middle,
child: Text(
'规格',
style: listTitleTextStyle,
),
),
TableCell(
verticalAlignment: TableCellVerticalAlignment.middle,
child: Text(
'单价',
style: listTitleTextStyle,
),
),
TableCell(
verticalAlignment: TableCellVerticalAlignment.middle,
child: Text(
'数量',
textAlign: TextAlign.right,
style: listTitleTextStyle,
),
),
]),
...controller.inventories.map((itemData) {
return TableRow(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(color: AppTheme.dividerColor))),
children: [
buildTableCell(
Text(
itemData.inventoryNumber!,
style: textStyle,
),
itemData: itemData),
buildTableCell(
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${itemData.product?.name}',
style: textStyle,
),
Text(
'${itemData.product?.company?.name}',
style: TextStyle(
fontSize: ScreenAdaper.sp(15),
color: AppTheme.grey),
)
],
),
itemData: itemData),
//
buildTableCell(
Text(itemData.product?.productSpecification ?? '',
style: textStyle),
itemData: itemData),
//
buildTableCell(
Text(
'${double.parse('${itemData.unitPrice}')}',
style: textStyle,
),
itemData: itemData),
//
buildTableCell(
Text(
'${itemData.quantity}${itemData.product?.unit?.label ?? ''}',
textAlign: TextAlign.right,
style: textStyle),
itemData: itemData,
alignment: Alignment.centerRight),
]);
}).toList()
])));
}
Widget buildInventoryList1() {
final textStyle = TextStyle(fontSize: ScreenAdaper.sp(22));
return Obx(
() => SmartRefresher(
enablePullDown: true,
enablePullUp: true,
controller: controller.refreshController,
onLoading: controller.onLoading,
onRefresh: controller.onRefresh,
child: ListView.separated(
itemCount: controller.inventories.length,
separatorBuilder: (context, index) => const Divider(
height: 1,
color: AppTheme.dividerColor,
),
itemBuilder: (context, index) {
final itemData = controller.inventories[index];
return InkWell(
onTap: () {
if (onInventorySelected != null)
onInventorySelected!(itemData);
},
child: Container(
padding:
EdgeInsets.symmetric(vertical: ScreenAdaper.height(10)),
child: Row(
children: [
Container(
//
constraints:
BoxConstraints(minWidth: ScreenAdaper.width(100)),
child: Text(
itemData.inventoryNumber!,
style: textStyle,
),
),
SizedBox(
width: ScreenAdaper.width(10),
),
Expanded(
flex: 8,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${itemData.product?.name}',
style: textStyle,
),
Text(
'${itemData.product?.company?.name}',
style: TextStyle(
fontSize: ScreenAdaper.sp(15),
color: AppTheme.grey),
)
],
)),
SizedBox(
width: ScreenAdaper.width(10),
),
//
Expanded(
child: Container(
constraints:
BoxConstraints(minWidth: ScreenAdaper.width(100)),
child: Text(
itemData.product?.productSpecification ?? '',
style: textStyle),
)),
SizedBox(
width: ScreenAdaper.width(10),
),
//
Container(
constraints:
BoxConstraints(minWidth: ScreenAdaper.width(100)),
child: Text(
'${double.parse('${itemData.unitPrice}')}${double.parse('${itemData.unitPrice}')}${double.parse('${itemData.unitPrice}')}${double.parse('${itemData.unitPrice}')}',
style: textStyle),
),
SizedBox(
width: ScreenAdaper.width(10),
),
//
Expanded(
child: Container(
alignment: Alignment.centerRight,
constraints:
BoxConstraints(minWidth: ScreenAdaper.width(80)),
child: Text(
'${itemData.quantity}${itemData.quantity}${itemData.product?.unit?.label ?? ''}',
style: textStyle),
))
],
),
),
);
}),
),
);
}
Widget buildTableCell(Widget child,
{required InventoryModel itemData, AlignmentGeometry? alignment}) {
return TableCell(
verticalAlignment: TableCellVerticalAlignment.middle,
child: InkWell(
onTap: () {
if (beforeSelectedCheck != null) {
if (!beforeSelectedCheck!(itemData)) {
return;
}
}
if (onInventorySelected != null) onInventorySelected!(itemData);
},
child: Container(
constraints: BoxConstraints(minHeight: ScreenAdaper.height(80)),
alignment: alignment ?? Alignment.centerLeft,
child: child),
),
);
}
}
class InventorySearchController extends GetxController {
RxList<InventoryModel> inventories = RxList([]);
RxString searchKey = ''.obs;
final searchBarTextConroller = TextEditingController();
RefreshController refreshController = RefreshController(initialRefresh: true);
int page = 1;
int limit = 15;
int total = 0;
RxInt hasInventoryStatus = RxInt(1);
Future<List<InventoryModel>> getData({bool isRefresh = false}) async {
if (isRefresh == true) {
page = 1;
} else {
page++;
}
final res = await Api.getInventory({
'page': page,
'pageSize': 15,
'keyword': searchKey.value,
'isCreateInout': true,
'isHasInventory': hasInventoryStatus.value,
});
List<InventoryModel> newList =
res.data!.items.map((e) => InventoryModel.fromJson(e)).toList();
isRefresh == true
? inventories.assignAll(newList)
: inventories.addAll(newList);
return newList;
}
Future<void> onRefresh() async {
await getData(isRefresh: true).then((_) {
refreshController.refreshCompleted(resetFooterState: true);
}).catchError((_) {
refreshController.refreshFailed();
});
}
Future<void> onLoading() async {
await getData().then((_) {
if (_.isEmpty) {
refreshController.loadNoData();
} else {
refreshController.loadComplete();
}
}).catchError((_) {
refreshController.loadFailed();
});
}
}

View File

@ -4,14 +4,18 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart'; import 'package:pull_to_refresh/pull_to_refresh.dart';
import 'package:sk_base_mobile/app_theme.dart'; import 'package:sk_base_mobile/app_theme.dart';
import 'package:sk_base_mobile/constants/bg_color.dart';
import 'package:sk_base_mobile/models/index.dart'; import 'package:sk_base_mobile/models/index.dart';
import 'package:sk_base_mobile/util/debouncer.dart'; import 'package:sk_base_mobile/util/debouncer.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart'; import 'package:sk_base_mobile/util/screen_adaper_util.dart';
import 'package:sk_base_mobile/apis/api.dart' as Api; import 'package:sk_base_mobile/apis/api.dart' as Api;
class ProductSearch extends StatelessWidget { class ProductSearch extends StatelessWidget {
ProductSearch({super.key}); Function(ProductModel)? onProductSelected;
ProductSearch({super.key, this.onProductSelected});
final controller = Get.put(ProductSearchController()); final controller = Get.put(ProductSearchController());
final listTitleTextStyle =
TextStyle(fontSize: ScreenAdaper.sp(20), fontWeight: FontWeight.w600);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
@ -19,20 +23,46 @@ class ProductSearch extends StatelessWidget {
horizontal: ScreenAdaper.width(20), horizontal: ScreenAdaper.width(20),
vertical: ScreenAdaper.height(20)), vertical: ScreenAdaper.height(20)),
child: Column( child: Column(
children: [buildSearchBar(), Expanded(child: buildProductList())], children: [
buildSearchBar(),
SizedBox(
height: ScreenAdaper.height(defaultPadding) / 2,
),
Row(
children: [
Text(
'产品编号',
style: listTitleTextStyle,
),
Expanded(
child: Text(
'名称',
textAlign: TextAlign.center,
style: listTitleTextStyle,
)),
Text(
'规格',
style: listTitleTextStyle,
)
],
),
Expanded(child: buildProductList())
],
), ),
); );
} }
Widget buildSearchBar() { Widget buildSearchBar() {
final doSearch = debouncer((_) { final doSearch = debouncer((String value) {
controller.searchKey.value = value;
controller.onRefresh(); controller.onRefresh();
}, delayTime: 500); }, delayTime: 500);
return Row( return Row(
children: [ children: [
Expanded( Expanded(
child: TextField( child: TextField(
controller: controller.searchBarController, controller: controller.searchBarTextConroller,
onChanged: (value) => doSearch(value), onChanged: (value) => doSearch(value),
decoration: InputDecoration( decoration: InputDecoration(
contentPadding: EdgeInsets.symmetric( contentPadding: EdgeInsets.symmetric(
@ -41,6 +71,17 @@ class ProductSearch extends StatelessWidget {
hintText: '名称,编号', hintText: '名称,编号',
floatingLabelBehavior: FloatingLabelBehavior.always, floatingLabelBehavior: FloatingLabelBehavior.always,
prefixIcon: const Icon(Icons.search), prefixIcon: const Icon(Icons.search),
// searchBarController有值时不显示
suffixIcon: Obx(() => controller.searchKey.value.isEmpty
? const SizedBox()
: IconButton(
icon: const Icon(Icons.clear),
onPressed: () {
controller.searchKey.value = '';
controller.searchBarTextConroller.clear();
doSearch('');
},
)),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
), ),
@ -66,16 +107,23 @@ class ProductSearch extends StatelessWidget {
itemBuilder: (context, index) { itemBuilder: (context, index) {
final itemData = controller.products[index]; final itemData = controller.products[index];
return InkWell( return InkWell(
onTap: () {}, onTap: () {
if (onProductSelected != null) onProductSelected!(itemData);
},
child: Container( child: Container(
padding: padding:
EdgeInsets.symmetric(vertical: ScreenAdaper.height(10)), EdgeInsets.symmetric(vertical: ScreenAdaper.height(10)),
child: Row( child: Row(
children: [ children: [
Text( Container(
//
constraints:
BoxConstraints(minWidth: ScreenAdaper.width(100)),
child: Text(
itemData.productNumber!, itemData.productNumber!,
style: TextStyle(fontSize: ScreenAdaper.sp(20)), style: TextStyle(fontSize: ScreenAdaper.sp(20)),
), ),
),
SizedBox( SizedBox(
width: ScreenAdaper.width(10), width: ScreenAdaper.width(10),
), ),
@ -95,6 +143,15 @@ class ProductSearch extends StatelessWidget {
) )
], ],
)), )),
SizedBox(
width: ScreenAdaper.width(10),
),
Container(
//
constraints:
BoxConstraints(minWidth: ScreenAdaper.width(100)),
child: Text(itemData.productSpecification ?? ''),
)
], ],
), ),
), ),
@ -107,7 +164,8 @@ class ProductSearch extends StatelessWidget {
class ProductSearchController extends GetxController { class ProductSearchController extends GetxController {
RxList<ProductModel> products = RxList([]); RxList<ProductModel> products = RxList([]);
TextEditingController searchBarController = TextEditingController(text: ''); RxString searchKey = ''.obs;
final searchBarTextConroller = TextEditingController();
RefreshController refreshController = RefreshController(initialRefresh: true); RefreshController refreshController = RefreshController(initialRefresh: true);
int page = 1; int page = 1;
int limit = 15; int limit = 15;
@ -119,7 +177,7 @@ class ProductSearchController extends GetxController {
page++; page++;
} }
final res = await Api.getProducts( final res = await Api.getProducts(
{'page': page, 'pageSize': 30, 'keyword': searchBarController.text}); {'page': page, 'pageSize': 30, 'keyword': searchKey.value});
List<ProductModel> newList = List<ProductModel> newList =
res.data!.items.map((e) => ProductModel.fromJson(e)).toList(); res.data!.items.map((e) => ProductModel.fromJson(e)).toList();
isRefresh == true ? products.assignAll(newList) : products.addAll(newList); isRefresh == true ? products.assignAll(newList) : products.addAll(newList);

View File

@ -1,34 +1,32 @@
import 'dart:io'; import 'dart:io';
import 'package:dropdown_search/dropdown_search.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_typeahead/flutter_typeahead.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:sk_base_mobile/app_theme.dart'; import 'package:sk_base_mobile/app_theme.dart';
import 'package:sk_base_mobile/constants/constants.dart'; import 'package:sk_base_mobile/constants/constants.dart';
import 'package:sk_base_mobile/constants/dict_enum.dart'; import 'package:sk_base_mobile/constants/dict_enum.dart';
import 'package:sk_base_mobile/models/index.dart'; import 'package:sk_base_mobile/models/index.dart';
import 'package:sk_base_mobile/screens/new_inventory_inout/components/date_time.dart'; import 'package:sk_base_mobile/models/inventory.model.dart';
import 'package:sk_base_mobile/screens/new_inventory_inout/components/inventory_search.dart';
import 'package:sk_base_mobile/screens/new_inventory_inout/components/product_search.dart'; import 'package:sk_base_mobile/screens/new_inventory_inout/components/product_search.dart';
import 'package:sk_base_mobile/store/dict.store.dart'; import 'package:sk_base_mobile/store/dict.store.dart';
import 'package:sk_base_mobile/util/date.util.dart'; import 'package:sk_base_mobile/util/util.dart';
import 'package:sk_base_mobile/util/modal.util.dart';
import 'package:sk_base_mobile/widgets/core/zt_bottomsheet_picker.dart';
import 'package:sk_base_mobile/widgets/core/zt_number_input.dart'; import 'package:sk_base_mobile/widgets/core/zt_number_input.dart';
import 'package:sk_base_mobile/widgets/core/zt_search_select.dart'; import 'package:sk_base_mobile/widgets/core/zt_search_select.dart';
import 'package:sk_base_mobile/widgets/core/zk_date_picker.dart'; import 'package:sk_base_mobile/widgets/core/zk_date_picker.dart';
import 'package:sk_base_mobile/widgets/core/zt_text_input.dart'; import 'package:sk_base_mobile/widgets/core/zt_text_input.dart';
import 'package:sk_base_mobile/widgets/empty.dart';
import 'package:sk_base_mobile/widgets/gradient_button.dart'; import 'package:sk_base_mobile/widgets/gradient_button.dart';
import 'package:sk_base_mobile/screens/new_inventory_inout/new_inventory_inout_controller.dart'; import 'package:sk_base_mobile/screens/new_inventory_inout/new_inventory_inout_controller.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart'; import 'package:sk_base_mobile/util/screen_adaper_util.dart';
class NewInventoryInout extends StatelessWidget { class NewInventoryInout extends StatelessWidget {
final String inOrOut; final NewInventoryInoutController controller;
NewInventoryInout({super.key, this.inOrOut = 'in'});
final controller = Get.put(NewInventoryInoutController());
final dictService = Get.find<DictService>(); final dictService = Get.find<DictService>();
final int inOrOut;
NewInventoryInout({Key? key, this.inOrOut = InventoryInOrOutEnum.In})
: controller = Get.put(NewInventoryInoutController(inOrOut: inOrOut)),
super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SafeArea( return SafeArea(
@ -51,6 +49,7 @@ class NewInventoryInout extends StatelessWidget {
children: [ children: [
buildForm(), buildForm(),
Obx(() => GradientButton( Obx(() => GradientButton(
buttonText: TextEnum.createInventoryInOutBtnText,
onPressed: () => {controller.create()}, onPressed: () => {controller.create()},
isLoading: controller.loading.value, isLoading: controller.loading.value,
)) ))
@ -65,15 +64,15 @@ class NewInventoryInout extends StatelessWidget {
final children = [ final children = [
Row(children: [ Row(children: [
Expanded( Expanded(
flex: 2, flex: 3,
child: buildProjectPicker(), child: buildProductPicker(),
), ),
SizedBox( SizedBox(
width: ScreenAdaper.width(formHorizontalGap), width: ScreenAdaper.width(formHorizontalGap),
), ),
Expanded( Expanded(
flex: 3, flex: 2,
child: buildProductPicker(), child: buildProjectPicker(),
), ),
]), ]),
SizedBox( SizedBox(
@ -82,20 +81,36 @@ class NewInventoryInout extends StatelessWidget {
Row( Row(
children: [ children: [
Expanded(flex: 1, child: buildQuantity()), Expanded(flex: 1, child: buildQuantity()),
if (inOrOut == InventoryInOrOutEnum.In) ...[
SizedBox( SizedBox(
width: ScreenAdaper.width(formHorizontalGap), width: ScreenAdaper.width(formHorizontalGap),
), ),
Expanded(flex: 1, child: buildDatePicker()), Expanded(flex: 1, child: buildUnitPrice()),
SizedBox( SizedBox(
width: ScreenAdaper.width(formHorizontalGap), width: ScreenAdaper.width(formHorizontalGap),
), ),
Expanded(flex: 1, child: buildAgent()), Expanded(flex: 1, child: buildAmount())
],
], ],
), ),
SizedBox( SizedBox(
height: ScreenAdaper.height(formVerticalGap), height: ScreenAdaper.height(formVerticalGap),
), ),
buildPositionBottomPicker(), Row(
children: [
Expanded(flex: 1, child: buildDatePicker()),
SizedBox(
width: ScreenAdaper.width(formHorizontalGap),
),
Expanded(flex: 1, child: buildAgent()),
if (inOrOut == InventoryInOrOutEnum.In) ...[
SizedBox(
width: ScreenAdaper.width(formHorizontalGap),
),
Expanded(flex: 1, child: buildPositionBottomPicker()),
]
],
),
SizedBox( SizedBox(
height: ScreenAdaper.height(formVerticalGap), height: ScreenAdaper.height(formVerticalGap),
), ),
@ -128,6 +143,7 @@ class NewInventoryInout extends StatelessWidget {
isRequired: true, isRequired: true,
textController: controller.projectTextController, textController: controller.projectTextController,
labelText: '项目', labelText: '项目',
hintText: inOrOut == InventoryInOrOutEnum.In ? '项目' : '请选择目标项目',
itemBuilder: (_, itemData) => Container( itemBuilder: (_, itemData) => Container(
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(
vertical: ScreenAdaper.height(15), vertical: ScreenAdaper.height(15),
@ -151,167 +167,71 @@ class NewInventoryInout extends StatelessWidget {
/// ///
Widget buildProductPicker() { Widget buildProductPicker() {
return TextFormField( return TextFormField(
controller: controller.positionTextController, controller: controller.productTextController,
decoration: const InputDecoration( decoration: InputDecoration(
labelText: '产品', hintText: '请选择产品',
label: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Text(
"*",
style: TextStyle(
color: Colors.red, fontSize: ScreenAdaper.sp(30)),
),
Text(
'产品',
style: TextStyle(fontSize: ScreenAdaper.sp(30)),
),
]),
floatingLabelBehavior: FloatingLabelBehavior.always), floatingLabelBehavior: FloatingLabelBehavior.always),
readOnly: true, readOnly: true,
onTap: () { onTap: () {
Get.generalDialog( if (inOrOut == InventoryInOrOutEnum.In) {
barrierLabel: "productPicker", ModalUtil.showGeneralDialog(content: ProductSearch(
barrierDismissible: true, onProductSelected: (ProductModel product) {
transitionDuration: const Duration(milliseconds: 400), Get.back();
pageBuilder: (_, __, ___) { String productName =
return Center( '${product.productNumber} ${product.name!}';
child: ClipRRect( controller.payload['productId'] = product.id;
borderRadius: BorderRadius.circular(30), if ((product.productSpecification ?? '').isNotEmpty) {
child: Material( productName += ' (${product.productSpecification})';
child: ClipRRect( }
borderRadius: const BorderRadius.only( controller.productTextController.text = productName;
topLeft: Radius.circular(
30,
),
topRight: Radius.circular(30)),
child: Container(
height: Get.height - ScreenAdaper.height(200),
width: Get.width - ScreenAdaper.width(200),
child: ProductSearch(),
),
))));
}, },
transitionBuilder: (_, anim, __, child) { ));
Tween<Offset> tween; } else {
tween = Tween(begin: const Offset(0, 1), end: Offset.zero); ModalUtil.showGeneralDialog(
return SlideTransition( content: InventorySearch(
position: tween.animate( beforeSelectedCheck: (InventoryModel itemData) {
CurvedAnimation(parent: anim, curve: Curves.easeInOut), final isValid = (itemData.quantity ?? 0) > 0;
), if (!isValid) {
child: child, SnackBarUtil().warning('库存不足');
); }
return (itemData.quantity ?? 0) > 0;
}, },
); onInventorySelected: (InventoryModel inventory) {
Get.back();
controller.payload['inventoryId'] = inventory.id;
String productName =
'${inventory.product!.name!}(¥${double.parse('${inventory.unitPrice}')})(${inventory.product?.company?.name})';
if ((inventory.product?.productSpecification ?? '')
.isNotEmpty) {
productName +=
' (${inventory.product?.productSpecification})';
}
controller.projectTextController.text =
inventory.project!.name!;
controller.payload['projectId'] = inventory.project?.id;
controller.quantityTextController.text =
'${inventory.quantity}';
controller.productTextController.text = productName;
controller.payload['productId'] = inventory.product?.id;
},
),
width: Get.width - ScreenAdaper.width(50));
}
}); });
// return DropdownSearch<ProductModel>(
// asyncItems: (String filter) async {
// return controller.getProducts(keyword: filter);
// },
// dropdownDecoratorProps: const DropDownDecoratorProps(
// dropdownSearchDecoration: InputDecoration(
// labelText: "产品",
// hintText: "请选择",
// floatingLabelBehavior: FloatingLabelBehavior.always),
// ),
// popupProps: PopupProps.menu(
// isFilterOnline: true,
// searchFieldProps: TextFieldProps(
// cursorColor: AppTheme.primaryColorLight,
// decoration: InputDecoration(
// contentPadding: EdgeInsets.symmetric(
// vertical: ScreenAdaper.height(15),
// horizontal: ScreenAdaper.width(10)))),
// searchDelay: const Duration(milliseconds: 500),
// emptyBuilder: (context, searchEntry) => const Empty(
// text: '未找到',
// ),
// itemBuilder: (_, itemData, _1) => Container(
// decoration: const BoxDecoration(
// border: Border(top: BorderSide(color: AppTheme.dividerColor))),
// padding: EdgeInsets.symmetric(
// vertical: ScreenAdaper.height(5),
// horizontal: ScreenAdaper.width(10)),
// child: Row(
// children: [
// Text(
// itemData.productNumber!,
// style: TextStyle(fontSize: ScreenAdaper.sp(20)),
// ),
// SizedBox(
// width: ScreenAdaper.width(10),
// ),
// Expanded(
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Text(
// '${itemData.name}',
// style: TextStyle(fontSize: ScreenAdaper.sp(20)),
// ),
// Text(
// '${itemData.company?.name}',
// style: TextStyle(
// fontSize: ScreenAdaper.sp(15), color: AppTheme.grey),
// )
// ],
// )),
// // SizedBox(
// // width: ScreenAdaper.width(10),
// // ),
// // Text(
// // itemData.productNumber!,
// // style: TextStyle(fontSize: ScreenAdaper.sp(20)),
// // ),
// ],
// ),
// ),
// showSearchBox: true,
// ),
// onChanged: print,
// // selectedItem: "Brazil",
// );
// return ZtSearchSelect<ProductModel>(
// isRequired: true,
// textController: controller.productTextController,
// labelText: '产品',
// itemBuilder: (_, itemData) => Container(
// padding: EdgeInsets.symmetric(
// vertical: ScreenAdaper.height(5),
// horizontal: ScreenAdaper.width(10)),
// child: Row(
// children: [
// Text(
// itemData.productNumber!,
// style: TextStyle(fontSize: ScreenAdaper.sp(20)),
// ),
// SizedBox(
// width: ScreenAdaper.width(10),
// ),
// Expanded(
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Text(
// '${itemData.name}',
// style: TextStyle(fontSize: ScreenAdaper.sp(20)),
// ),
// Text(
// '${itemData.company?.name}',
// style: TextStyle(
// fontSize: ScreenAdaper.sp(15),
// color: AppTheme.grey),
// )
// ],
// )),
// // SizedBox(
// // width: ScreenAdaper.width(10),
// // ),
// // Text(
// // itemData.productNumber!,
// // style: TextStyle(fontSize: ScreenAdaper.sp(20)),
// // ),
// ],
// ),
// ),
// suggestionsCallback: (String keyword) {
// return controller.getProducts(keyword: keyword);
// },
// onClear: () {
// controller.payload.remove('productId');
// },
// onSelected: (ProductModel product) {
// controller.productTextController.text = product.name ?? '';
// controller.payload['productId'] = product.id;
// });
} }
/// ///
@ -335,10 +255,35 @@ class NewInventoryInout extends StatelessWidget {
return ZtNumberInput( return ZtNumberInput(
textController: controller.quantityTextController, textController: controller.quantityTextController,
labelText: '数量', labelText: '数量',
onChanged: (value) {
controller.calculatePrice('quantity');
},
isRequired: true, isRequired: true,
); );
} }
///
Widget buildUnitPrice() {
return ZtNumberInput(
textController: controller.unitPriceTextController,
labelText: '单价',
onChanged: (value) {
controller.calculatePrice('unitPrice');
},
);
}
///
Widget buildAmount() {
return ZtNumberInput(
textController: controller.amountTextController,
labelText: '金额',
onChanged: (value) {
controller.calculatePrice('amount');
},
);
}
/// ///
Widget buildAgent() { Widget buildAgent() {
return ZtTextInput( return ZtTextInput(
@ -405,23 +350,9 @@ class NewInventoryInout extends StatelessWidget {
} }
// , ${secondLevelData[_1].label}, ${thirdtLevelData[_2].label} // , ${secondLevelData[_1].label}, ${thirdtLevelData[_2].label}
controller.positionTextController.text = position; controller.positionTextController.text = position;
controller.payload['position'] = position;
}); });
},
onChanged: (_) {
printInfo(info: _);
}); });
// return ZtTextInput(
// textController: controller.agentTextController,
// labelText: '库存位置',
// isRequired: true,
// onTap: () => ModalUtil.showBottomSheetPicker(
// title: '库存位置',
// firstLevel: dictService
// .getDictItemsByCode(DictTypeEnum.InventoryRoom)
// .map((e) => Text(e.label))
// .toList(),
// onChanged: () {}),
// );
} }
/// ///
@ -434,7 +365,7 @@ class NewInventoryInout extends StatelessWidget {
child: Column( child: Column(
children: [ children: [
Text( Text(
'*请拍照上传产品照片', '*请拍照上传照片',
style: TextStyle( style: TextStyle(
fontSize: ScreenAdaper.sp(25), fontSize: ScreenAdaper.sp(25),
color: AppTheme.secondPrimaryColor), color: AppTheme.secondPrimaryColor),

View File

@ -1,26 +1,22 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:decimal/decimal.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
import 'package:sk_base_mobile/constants/enum.dart';
import 'package:sk_base_mobile/db_helper/dbHelper.dart'; import 'package:sk_base_mobile/db_helper/dbHelper.dart';
import 'package:sk_base_mobile/models/base_response.dart'; import 'package:sk_base_mobile/models/index.dart';
import 'package:sk_base_mobile/models/product.model.dart';
import 'package:sk_base_mobile/models/project.model.dart';
import 'package:sk_base_mobile/models/task_model.dart';
import 'package:sk_base_mobile/screens/inventory_inout/inventory_inout_controller.dart'; import 'package:sk_base_mobile/screens/inventory_inout/inventory_inout_controller.dart';
import 'package:sk_base_mobile/apis/api.dart' as Api; import 'package:sk_base_mobile/apis/api.dart' as Api;
import 'package:sk_base_mobile/util/date.util.dart'; import 'package:sk_base_mobile/util/date.util.dart';
import 'package:sk_base_mobile/util/loading_util.dart';
import 'package:sk_base_mobile/util/logger_util.dart'; import 'package:sk_base_mobile/util/logger_util.dart';
import 'package:sk_base_mobile/util/media_util.dart'; import 'package:sk_base_mobile/util/media_util.dart';
import 'package:sk_base_mobile/util/photo_picker_util.dart';
import 'package:sk_base_mobile/util/snack_bar.util.dart'; import 'package:sk_base_mobile/util/snack_bar.util.dart';
class NewInventoryInoutController extends GetxController { class NewInventoryInoutController extends GetxController {
final formKey = GlobalKey<FormState>(); final formKey = GlobalKey<FormState>();
final int? inOrOut;
DateTime? pickedDate; DateTime? pickedDate;
final DbHelper db = DbHelper(); final DbHelper db = DbHelper();
RxInt selectedImage = 0.obs; RxInt selectedImage = 0.obs;
@ -42,12 +38,15 @@ class NewInventoryInoutController extends GetxController {
final productTextController = TextEditingController(); final productTextController = TextEditingController();
final dateTextController = TextEditingController(); final dateTextController = TextEditingController();
final quantityTextController = TextEditingController(); final quantityTextController = TextEditingController();
final unitPriceTextController = TextEditingController();
final amountTextController = TextEditingController();
final agentTextController = TextEditingController(); final agentTextController = TextEditingController();
final remarkTextController = TextEditingController(); final remarkTextController = TextEditingController();
final positionTextController = TextEditingController(); final positionTextController = TextEditingController();
final uploadImgFilesPath = <String>[].obs; final uploadImgFilesPath = <String>[].obs;
final uploadScrollController = ScrollController(); final uploadScrollController = ScrollController();
Map<String, dynamic> payload = {}; Map<String, dynamic> payload = {};
NewInventoryInoutController({this.inOrOut});
Future<List<ProjectModel>> getProjects({String? keyword}) async { Future<List<ProjectModel>> getProjects({String? keyword}) async {
final res = final res =
@ -73,10 +72,47 @@ class NewInventoryInoutController extends GetxController {
onReady() { onReady() {
super.onReady(); super.onReady();
dateTextController.text = DateUtil.format(DateTime.now()); dateTextController.text = DateUtil.format(DateTime.now());
payload['time'] = DateUtil.format(DateTime.now());
payload['inOrOut'] = inOrOut;
}
///
void calculatePrice(String changedField) {
Decimal quantity = Decimal.fromInt(0);
Decimal unitPrice = Decimal.fromInt(0);
Decimal amount = Decimal.fromInt(0);
if (quantityTextController.text.isNotEmpty) {
quantity = Decimal.parse(quantityTextController.text);
}
if (unitPriceTextController.text.isNotEmpty) {
unitPrice = Decimal.parse(unitPriceTextController.text);
}
if (amountTextController.text.isNotEmpty) {
amount = Decimal.parse(amountTextController.text);
}
if (InventoryInOrOutEnum.Out == inOrOut) {
if (changedField != 'amount') {
Decimal result = unitPrice * quantity;
amountTextController.text =
result != Decimal.zero ? result.toString() : '';
}
} else {
//
if (changedField != 'unitPrice' && quantity != Decimal.zero) {
Decimal result =
(amount / quantity).toDecimal(scaleOnInfinitePrecision: 10);
unitPriceTextController.text =
result != Decimal.zero ? result.toString() : '';
} else if (changedField != 'amount') {
Decimal result = (unitPrice * quantity);
amountTextController.text =
result != Decimal.zero ? result.toString() : '';
}
}
} }
/// ///
create() { Future<void> create() async {
if (payload['projectId'] == null) { if (payload['projectId'] == null) {
SnackBarUtil().info( SnackBarUtil().info(
'项目不能为空', '项目不能为空',
@ -109,7 +145,48 @@ class NewInventoryInoutController extends GetxController {
); );
return; return;
} }
LoggerUtil().info(payload); if (unitPriceTextController.text.isEmpty) {
payload.remove('unitPrice');
} else {
payload['unitPrice'] = unitPriceTextController.text;
}
if (amountTextController.text.isEmpty) {
payload.remove('amount');
} else {
payload['amount'] = amountTextController.text;
}
payload['remark'] = remarkTextController.text;
loading.value = true;
// uploadImgFilesPath
try {
// final recordId = res.data as int;
//
final uploadRes = await Future.wait(uploadImgFilesPath
.map((filePath) => MediaUtil().uploadImg(File(filePath),
bussinessModule: StorageBussinessModuleEnum.MaterialsInOut,
bussinessRecordId: null))
.toList());
List<int> fileIds = [];
if (uploadRes.isNotEmpty) {
fileIds = uploadRes.map((e) => e!.id!).toList();
}
final response = await Api.createInventoryInout(payload);
//
if (fileIds.isNotEmpty) {
await Api.updateInventoryInout(
response.data, {'fileIds': uploadRes.map((e) => e!.id).toList()});
}
Get.back(result: true);
SnackBarUtil().success(
'提交成功',
);
} catch (e) {
LoggerUtil().error(e);
} finally {
loading.value = false;
}
} }
Future<void> photoPicker() async { Future<void> photoPicker() async {
@ -250,7 +327,7 @@ class NewInventoryInoutController extends GetxController {
// .then((value) { // .then((value) {
// Duration dif = pickedDate!.difference(DateTime( // Duration dif = pickedDate!.difference(DateTime(
// DateTime.now().year, DateTime.now().month, DateTime.now().day)); // DateTime.now().year, DateTime.now().month, DateTime.now().day));
// inventoryInoutController.list[dif.inDays].add(value); // inventoryInoutlist[dif.inDays].add(value);
// Timer(const Duration(seconds: 1), () { // Timer(const Duration(seconds: 1), () {
// loading.value = false; // loading.value = false;
// Get.back(); // Get.back();

View File

@ -1,5 +1,4 @@
import 'dart:convert'; import 'dart:convert';
import 'package:get/get.dart' as Get; import 'package:get/get.dart' as Get;
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:sk_base_mobile/models/base_response.dart'; import 'package:sk_base_mobile/models/base_response.dart';
@ -42,9 +41,10 @@ class DioService extends Get.GetxService {
} }
if (e.type == DioExceptionType.connectionTimeout) { if (e.type == DioExceptionType.connectionTimeout) {
// It occurs when url is opened timeout. // It occurs when url is opened timeout.
await SnackBarUtil().error( await SnackBarUtil().error(TextEnum.serverErrorMsg);
'The server is busy, please try again.', } else if (e.type == DioExceptionType.connectionError) {
); // It occurs when url is opened timeout.
await SnackBarUtil().error(TextEnum.serverErrorMsg);
} else if (e.type == DioExceptionType.sendTimeout) { } else if (e.type == DioExceptionType.sendTimeout) {
// It occurs when url is sent timeout. // It occurs when url is sent timeout.
// await SnackBarUtil().error( // await SnackBarUtil().error(
@ -134,18 +134,18 @@ class DioService extends Get.GetxService {
return; return;
} }
if (response.data != null) { if (response.data != null) {
try {
if (response.data['code'] == 200) { if (response.data['code'] == 200) {
if (GloablConfig.DEBUG) LoggerUtil().info(response.data['data']); if (GloablConfig.DEBUG) LoggerUtil().info(response.data['data']);
response.data = response.data['data']; response.data = response.data['data'];
// //
if (response.data['meta'] != null && response.data['items'] != null) { if (response.data != null &&
response.data is Map &&
response.data['meta'] != null &&
response.data['items'] != null) {
response.data = PaginationData.fromJson(response.data); response.data = PaginationData.fromJson(response.data);
} }
} } else {
} catch (e) { await SnackBarUtil().error(response.data['message']);
printError(info: e.toString());
} }
} }
handler.next(response); handler.next(response);

View File

@ -2,6 +2,7 @@ import 'dart:io';
import 'package:dio/dio.dart' as Dio; import 'package:dio/dio.dart' as Dio;
import 'package:sk_base_mobile/apis/api.dart' as Api; import 'package:sk_base_mobile/apis/api.dart' as Api;
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
import 'package:sk_base_mobile/models/upload_result.model.dart';
import 'package:sk_base_mobile/services/service.dart'; import 'package:sk_base_mobile/services/service.dart';
class MediaUtil { class MediaUtil {
@ -35,12 +36,13 @@ class MediaUtil {
return pickedFile; return pickedFile;
} }
Future<String> uploadImg(File imgfile) async { Future<UploadResultModel?> uploadImg(File imgfile,
{int? bussinessRecordId, String? bussinessModule}) async {
Dio.Response response = await Api.uploadImg(imgfile); Dio.Response response = await Api.uploadImg(imgfile);
if (response.data != null && response.data['data']['filename'] != null) { if (response.data != null && response.data['filename'] != null) {
return response.data["data"]["filename"]; return UploadResultModel.fromJson(response.data["filename"]);
} }
return ''; return null;
} }
// // // //

View File

@ -2,6 +2,7 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
import 'package:sk_base_mobile/widgets/core/zt_bottomsheet_picker.dart'; import 'package:sk_base_mobile/widgets/core/zt_bottomsheet_picker.dart';
class ModalUtil { class ModalUtil {
@ -65,4 +66,34 @@ class ModalUtil {
popupHeight: popupHeight ?? 400); popupHeight: popupHeight ?? 400);
}, },
); );
static Future<void> showGeneralDialog(
{required Widget content, double? width}) {
return Get.generalDialog(
barrierLabel: "productPicker",
barrierDismissible: true,
transitionDuration: const Duration(milliseconds: 400),
pageBuilder: (_, __, ___) {
return Center(
child: ClipRRect(
borderRadius: BorderRadius.circular(ScreenAdaper.sp(30)),
child: Material(
child: SizedBox(
height: Get.height - ScreenAdaper.height(150),
width: width ?? Get.width - ScreenAdaper.width(150),
child: content,
))));
},
transitionBuilder: (_, anim, __, child) {
Tween<Offset> tween;
tween = Tween(begin: const Offset(0, 1), end: Offset.zero);
return SlideTransition(
position: tween.animate(
CurvedAnimation(parent: anim, curve: Curves.easeInOut),
),
child: child,
);
},
);
}
} }

View File

@ -9,13 +9,16 @@ import 'package:sk_base_mobile/util/screen_adaper_util.dart';
class ZtNumberInput extends StatelessWidget { class ZtNumberInput extends StatelessWidget {
final TextEditingController textController; final TextEditingController textController;
final VoidCallback? onTap; final VoidCallback? onTap;
final Function(String)? onChanged;
final bool isRequired; final bool isRequired;
final String labelText; final String labelText;
final String? hint; final String? hint;
ZtNumberInput( const ZtNumberInput(
{super.key, {super.key,
required this.textController, required this.textController,
this.onTap, this.onTap,
this.onChanged,
this.hint, this.hint,
this.isRequired = false, this.isRequired = false,
this.labelText = ''}); this.labelText = ''});
@ -26,15 +29,16 @@ class ZtNumberInput extends StatelessWidget {
onTapOutside: (event) { onTapOutside: (event) {
FocusScope.of(context).unfocus(); FocusScope.of(context).unfocus();
}, },
onChanged: onChanged ?? (_) {},
onTap: onTap ?? () {}, onTap: onTap ?? () {},
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle(fontSize: ScreenAdaper.sp(30)),
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[ inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.digitsOnly // //
FilteringTextInputFormatter.allow(RegExp(r'^\d+\.?\d{0,10}')),
], ],
decoration: InputDecoration( decoration: InputDecoration(
contentPadding: EdgeInsets.symmetric(vertical: ScreenAdaper.height(18)), // contentPadding: EdgeInsets.symmetric(vertical: ScreenAdaper.height(18)),
floatingLabelBehavior: FloatingLabelBehavior.always, floatingLabelBehavior: FloatingLabelBehavior.always,
label: Row( label: Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,

View File

@ -10,6 +10,8 @@ class ZtSearchSelect<T> extends StatelessWidget {
final void Function(T)? onSelected; final void Function(T)? onSelected;
final VoidCallback? onClear; final VoidCallback? onClear;
final String labelText; final String labelText;
final String hintText;
final bool isRequired; final bool isRequired;
final Widget Function(BuildContext, T) itemBuilder; final Widget Function(BuildContext, T) itemBuilder;
const ZtSearchSelect( const ZtSearchSelect(
@ -18,6 +20,7 @@ class ZtSearchSelect<T> extends StatelessWidget {
required this.labelText, required this.labelText,
required this.itemBuilder, required this.itemBuilder,
required this.suggestionsCallback, required this.suggestionsCallback,
this.hintText = '请选择',
this.onSelected, this.onSelected,
this.onClear, this.onClear,
this.isRequired = false}); this.isRequired = false});
@ -61,7 +64,7 @@ class ZtSearchSelect<T> extends StatelessWidget {
) )
: const SizedBox(), : const SizedBox(),
floatingLabelBehavior: FloatingLabelBehavior.always, floatingLabelBehavior: FloatingLabelBehavior.always,
hintText: '请选择', hintText: hintText,
label: Row( label: Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,

View File

@ -42,7 +42,7 @@ class _FadeInCacheImageState extends State<FadeInCacheImage> {
theContext = context; theContext = context;
if ((widget.url == null || widget.url == '' || widget.url == 'null')) { if ((widget.url == null || widget.url == '' || widget.url == 'null')) {
return Container( return SizedBox(
width: widget.width, width: widget.width,
height: widget.height, height: widget.height,
child: Icon(Icons.image_not_supported, child: Icon(Icons.image_not_supported,
@ -51,7 +51,6 @@ class _FadeInCacheImageState extends State<FadeInCacheImage> {
} }
return buildImg(widget.url); return buildImg(widget.url);
;
} }
// Future getImg() { // Future getImg() {
@ -66,13 +65,15 @@ class _FadeInCacheImageState extends State<FadeInCacheImage> {
height: widget.height, height: widget.height,
fit: widget.fit, fit: widget.fit,
placeholder: (context, url) => Container( placeholder: (context, url) => Container(
decoration: BoxDecoration(color: AppTheme.grey), decoration: const BoxDecoration(color: AppTheme.grey),
child: CupertinoActivityIndicator(), child: const CupertinoActivityIndicator(),
),
errorWidget: (context, error, stackTrace) => SizedBox(
width: widget.width,
height: widget.height,
child: Icon(Icons.image_not_supported,
size: ScreenAdaper.sp(100), color: AppTheme.grey),
), ),
errorWidget: (context, error, stackTrace) => Image(
image: AssetImage(
'assets/images/deer_detail_banner1.jpg',
)),
); );
} }
} }

View File

@ -3,6 +3,7 @@ import 'package:sk_base_mobile/apis/api.dart' as Api;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
import 'package:sk_base_mobile/models/upload_result.model.dart';
import 'package:sk_base_mobile/store/auth.store.dart'; import 'package:sk_base_mobile/store/auth.store.dart';
import 'package:sk_base_mobile/util/media_util.dart'; import 'package:sk_base_mobile/util/media_util.dart';
import 'package:sk_base_mobile/util/photo_picker_util.dart'; import 'package:sk_base_mobile/util/photo_picker_util.dart';
@ -83,15 +84,15 @@ class MyAvatarController extends GetxController {
await PhotoPickerUtil().showPicker(callback: (XFile pickedFile) async { await PhotoPickerUtil().showPicker(callback: (XFile pickedFile) async {
await LoadingUtil.to.show(status: 'Uploading...'); await LoadingUtil.to.show(status: 'Uploading...');
try { try {
String? filename = await MediaUtil().uploadImg(File(pickedFile.path)); // UploadResultModel? res = await MediaUtil().uploadImg(File(pickedFile.path));
if (filename.isNotEmpty) { // if (res.pa.isNotEmpty) {
final res = await Api.updateAvatar(filename); // final res = await Api.updateAvatar(filename);
if (res.data != null) { // if (res.data != null) {
SnackBarUtil().success('Update avatar successfully'); // SnackBarUtil().success('Update avatar successfully');
} // }
} // }
uploadImgFilePath(pickedFile.path); // uploadImgFilePath(pickedFile.path);
Get.back(); // Get.back();
} catch (e) { } catch (e) {
SnackBarUtil().error('Update avatar failed.'); SnackBarUtil().error('Update avatar failed.');
} finally { } finally {

View File

@ -169,6 +169,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.7" version: "2.0.7"
decimal:
dependency: "direct main"
description:
name: decimal
sha256: "24a261d5d5c87e86c7651c417a5dbdf8bcd7080dd592533910e8d0505a279f21"
url: "https://pub.dev"
source: hosted
version: "2.3.3"
dio: dio:
dependency: "direct main" dependency: "direct main"
description: description:
@ -177,14 +185,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.4.1" version: "5.4.1"
dropdown_search:
dependency: "direct main"
description:
name: dropdown_search
sha256: "55106e8290acaa97ed15bea1fdad82c3cf0c248dd410e651f5a8ac6870f783ab"
url: "https://pub.dev"
source: hosted
version: "5.0.6"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
@ -744,6 +744,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.0" version: "2.0.0"
rational:
dependency: transitive
description:
name: rational
sha256: ba58e9e18df9abde280e8b10051e4bce85091e41e8e7e411b6cde2e738d357cf
url: "https://pub.dev"
source: hosted
version: "2.2.2"
rxdart: rxdart:
dependency: transitive dependency: transitive
description: description:

View File

@ -57,7 +57,7 @@ dependencies:
path: ^1.8.3 path: ^1.8.3
path_provider: ^2.1.2 path_provider: ^2.1.2
flutter_typeahead: ^5.2.0 flutter_typeahead: ^5.2.0
dropdown_search: ^5.0.6 decimal: ^2.3.3
dev_dependencies: dev_dependencies: