feat: inventory inout feature finsihed
This commit is contained in:
parent
7986fde5d3
commit
fc0187acf0
|
@ -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) {
|
||||||
|
|
|
@ -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';
|
||||||
|
}
|
||||||
|
|
|
@ -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';
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 = ' 服务器繁忙,请重试或者联系管理员';
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(),
|
||||||
|
};
|
||||||
|
}
|
|
@ -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,
|
||||||
|
};
|
||||||
|
}
|
|
@ -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(
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// // 上传一张照片
|
// // 上传一张照片
|
||||||
|
|
|
@ -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,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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',
|
|
||||||
)),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
24
pubspec.lock
24
pubspec.lock
|
@ -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:
|
||||||
|
|
|
@ -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:
|
||||||
|
|
Loading…
Reference in New Issue