feat: project filter for inventory management

This commit is contained in:
louis 2024-03-29 14:42:15 +08:00
parent 88be83aceb
commit 8a06486f93
7 changed files with 194 additions and 75 deletions

View File

@ -1,5 +1,6 @@
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/snack_bar.util.dart';
import '../../store/auth.store.dart'; import '../../store/auth.store.dart';
// import 'package:sentry/sentry.dart'; // import 'package:sentry/sentry.dart';
@ -11,10 +12,10 @@ class LoginController extends GetxController {
String password = ''; String password = '';
bool loading = false; bool loading = false;
Future<void> doLogin() async { Future<void> doLogin() async {
if (!formKey.currentState!.validate()) { if (username.isEmpty || password.isEmpty) {
SnackBarUtil().warning('请填写用户名和密码');
return; return;
} }
// form中的数据 // form中的数据
AuthStore.to.login(username: username, password: password); AuthStore.to.login(username: username, password: password);
} }

View File

@ -180,11 +180,6 @@ class LoginScreen extends StatelessWidget {
Icons.person_2_outlined, Icons.person_2_outlined,
size: ScreenAdaper.height(40), size: ScreenAdaper.height(40),
), ),
errorStyle: TextStyle(fontSize: ScreenAdaper.height(20)),
contentPadding: EdgeInsets.symmetric(
vertical: ScreenAdaper.height(10),
horizontal: ScreenAdaper.width(30),
),
hintText: '用户名', hintText: '用户名',
hintStyle: TextStyle(fontSize: ScreenAdaper.height(25)), hintStyle: TextStyle(fontSize: ScreenAdaper.height(25)),
border: InputBorder.none, border: InputBorder.none,
@ -197,7 +192,6 @@ class LoginScreen extends StatelessWidget {
onChanged: (value) { onChanged: (value) {
_controller.username = value; _controller.username = value;
}, },
validator: (value) => value!.isEmpty ? '用户名不能为空' : null,
); );
} }
@ -225,7 +219,6 @@ class LoginScreen extends StatelessWidget {
_controller.password = value; _controller.password = value;
}, },
autovalidateMode: AutovalidateMode.onUserInteraction, autovalidateMode: AutovalidateMode.onUserInteraction,
validator: (value) => value!.isEmpty ? '密码不能为空' : null,
); );
} }

View File

@ -10,6 +10,7 @@ import 'package:sk_base_mobile/models/index.dart';
import 'package:sk_base_mobile/models/inventory.model.dart'; import 'package:sk_base_mobile/models/inventory.model.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/widgets/core/zt_search_select.dart';
import 'package:sk_base_mobile/widgets/empty.dart'; import 'package:sk_base_mobile/widgets/empty.dart';
class InventorySearch extends StatelessWidget { class InventorySearch extends StatelessWidget {
@ -23,19 +24,23 @@ class InventorySearch extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(
horizontal: ScreenAdaper.width(20), horizontal: ScreenAdaper.width(20),
vertical: ScreenAdaper.height(20)), vertical: ScreenAdaper.height(20)),
child: Column( child: GestureDetector(
children: [ onTap: () {
buildSearchBar(), FocusScope.of(context).unfocus();
SizedBox( },
height: ScreenAdaper.height(defaultPadding) / 2, child: Column(
children: [
buildSearchBar(),
SizedBox(
height: ScreenAdaper.height(defaultPadding) / 2,
),
Expanded(child: buildInventoryList())
],
), ),
Expanded(child: buildInventoryList()) ));
],
),
);
} }
Widget buildSearchBar() { Widget buildSearchBar() {
@ -74,13 +79,76 @@ class InventorySearch extends StatelessWidget {
), ),
))), ))),
SizedBox( SizedBox(
width: ScreenAdaper.width(20), width: ScreenAdaper.width(5),
),
buildProjectPicker(),
SizedBox(
width: ScreenAdaper.width(5),
), ),
buildHasInventoryPicker() buildHasInventoryPicker()
], ],
); );
} }
//
Widget buildProjectPicker() {
return Container(
width: ScreenAdaper.width(250),
constraints: BoxConstraints(minWidth: ScreenAdaper.width(250)),
child: Obx(
() => DropdownButtonFormField<int>(
value: controller.projectId.value,
isExpanded: true,
items: [
DropdownMenuItem(
child: Text('所有项目'),
value: 0,
),
...controller.projects.map((element) => DropdownMenuItem(
child: Text('${element.name}'), value: element.id))
],
onChanged: (value) {
controller.projectId.value = value;
controller.refreshController.requestRefresh();
},
decoration: InputDecoration(
contentPadding: EdgeInsets.symmetric(
vertical: ScreenAdaper.height(15),
horizontal: ScreenAdaper.width(15)),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(ScreenAdaper.sp(15)),
),
)),
));
// return Container(
// width: ScreenAdaper.width(200),
// constraints: BoxConstraints(minWidth: ScreenAdaper.width(200)),
// child: ZtSearchSelect<ProjectModel>(
// isRequired: true,
// contentPadding:
// EdgeInsets.symmetric(horizontal: ScreenAdaper.width(15)),
// textController: controller.projectTextController,
// hintText: '请选择所属项目',
// itemBuilder: (_, itemData) => Container(
// padding: EdgeInsets.symmetric(
// vertical: ScreenAdaper.height(15),
// horizontal: ScreenAdaper.width(20)),
// child: Text(
// '${itemData.name}',
// style: TextStyle(fontSize: ScreenAdaper.height(20)),
// )),
// suggestionsCallback: (String keyword) {
// return controller.getProjects(keyword: keyword);
// },
// onClear: () {
// controller.projectId = null;
// },
// onSelected: (ProjectModel project) {
// controller.projectTextController.text = project.name ?? '';
// controller.projectId = project.id;
// }));
}
Widget buildHasInventoryPicker() { Widget buildHasInventoryPicker() {
return Container( return Container(
width: ScreenAdaper.width(200), width: ScreenAdaper.width(200),
@ -134,14 +202,19 @@ class InventorySearch extends StatelessWidget {
: Table(columnWidths: { : Table(columnWidths: {
0: MinColumnWidth( 0: MinColumnWidth(
FixedColumnWidth(80), FixedColumnWidth(80)), FixedColumnWidth(80), FixedColumnWidth(80)),
1: FlexColumnWidth(ScreenAdaper.screenShortDistance() / 4), 1: MinColumnWidth(
2: FlexColumnWidth(ScreenAdaper.screenShortDistance() / 4),
3: MinColumnWidth(
FixedColumnWidth( FixedColumnWidth(
ScreenAdaper.screenShortDistance() / 5), ScreenAdaper.screenShortDistance() / 5),
FixedColumnWidth( FixedColumnWidth(
ScreenAdaper.screenShortDistance() / 5)), ScreenAdaper.screenShortDistance() / 5)),
2: FlexColumnWidth(ScreenAdaper.screenShortDistance() / 4),
3: FlexColumnWidth(ScreenAdaper.screenShortDistance() / 4),
4: MinColumnWidth( 4: MinColumnWidth(
FixedColumnWidth(
ScreenAdaper.screenShortDistance() / 5),
FixedColumnWidth(
ScreenAdaper.screenShortDistance() / 5)),
5: MinColumnWidth(
FixedColumnWidth( FixedColumnWidth(
ScreenAdaper.screenShortDistance() / 6), ScreenAdaper.screenShortDistance() / 6),
FixedColumnWidth( FixedColumnWidth(
@ -165,6 +238,13 @@ class InventorySearch extends StatelessWidget {
style: listTitleTextStyle, style: listTitleTextStyle,
), ),
)), )),
TableCell(
verticalAlignment:
TableCellVerticalAlignment.middle,
child: Text(
'所属项目',
style: listTitleTextStyle,
)),
TableCell( TableCell(
verticalAlignment: verticalAlignment:
TableCellVerticalAlignment.middle, TableCellVerticalAlignment.middle,
@ -211,6 +291,21 @@ class InventorySearch extends StatelessWidget {
style: textStyle, style: textStyle,
), ),
itemData: itemData), itemData: itemData),
//
buildTableCell(
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${itemData.project?.name}',
style: textStyle,
),
],
),
itemData: itemData),
//
buildTableCell( buildTableCell(
Column( Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@ -282,6 +377,7 @@ class InventorySearch extends StatelessWidget {
class InventorySearchController extends GetxController { class InventorySearchController extends GetxController {
RxList<InventoryModel> inventories = RxList([]); RxList<InventoryModel> inventories = RxList([]);
final projectTextController = TextEditingController();
RxString searchKey = ''.obs; RxString searchKey = ''.obs;
final searchBarTextConroller = TextEditingController(); final searchBarTextConroller = TextEditingController();
RefreshController refreshController = RefreshController(initialRefresh: true); RefreshController refreshController = RefreshController(initialRefresh: true);
@ -289,6 +385,14 @@ class InventorySearchController extends GetxController {
int limit = 15; int limit = 15;
int total = 0; int total = 0;
RxInt hasInventoryStatus = RxInt(1); RxInt hasInventoryStatus = RxInt(1);
RxList<ProjectModel> projects = <ProjectModel>[].obs;
RxnInt projectId = RxnInt(0);
@override
onReady() {
super.onReady();
getProjects();
}
Future<List<InventoryModel>> getData({bool isRefresh = false}) async { Future<List<InventoryModel>> getData({bool isRefresh = false}) async {
if (isRefresh == true) { if (isRefresh == true) {
page = 1; page = 1;
@ -299,6 +403,7 @@ class InventorySearchController extends GetxController {
'page': page, 'page': page,
'pageSize': 15, 'pageSize': 15,
'keyword': searchKey.value, 'keyword': searchKey.value,
'projectId': projectId.value == 0 ? null : projectId.value,
'isCreateInout': true, 'isCreateInout': true,
'isHasInventory': hasInventoryStatus.value, 'isHasInventory': hasInventoryStatus.value,
}); });
@ -311,6 +416,16 @@ class InventorySearchController extends GetxController {
return newList; return newList;
} }
Future<List<ProjectModel>> getProjects({String? keyword}) async {
final res =
await Api.getProjects({'page': 1, 'pageSize': 10, 'name': keyword});
if (res.data != null) {
projects.assignAll(
res.data!.items.map((e) => ProjectModel.fromJson(e)).toList());
}
return projects;
}
Future<void> onRefresh() async { Future<void> onRefresh() async {
await getData(isRefresh: true).then((_) { await getData(isRefresh: true).then((_) {
refreshController.refreshCompleted(resetFooterState: true); refreshController.refreshCompleted(resetFooterState: true);

View File

@ -49,11 +49,10 @@ class NewInventoryInout extends StatelessWidget {
child: Column( child: Column(
children: [ children: [
buildForm(), buildForm(),
Obx(() => GradientButton( GradientButton(
buttonText: TextEnum.createInventoryInOutBtnText, buttonText: TextEnum.createInventoryInOutBtnText,
onPressed: () => {controller.create()}, onPressed: () => {controller.create()},
isLoading: controller.loading.value, )
))
], ],
), ),
)))); ))));
@ -455,15 +454,14 @@ class NewInventoryInout extends StatelessWidget {
height: ScreenAdaper.height(10), height: ScreenAdaper.height(10),
), ),
Obx(() => SingleChildScrollView( Obx(() => SingleChildScrollView(
controller: controller.uploadScrollController, controller: controller.productUploadScrollController,
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
...controller.uploadProductImgFilesPath ...controller.uploadProductImgFilesPath.map(
.map((String path) => (String path) =>
builderImagePreview(path, 'product')) builderImagePreview(path, 'product')),
.toList(),
buildImageUploader('product') buildImageUploader('product')
], ],
), ),
@ -483,15 +481,13 @@ class NewInventoryInout extends StatelessWidget {
height: ScreenAdaper.height(10), height: ScreenAdaper.height(10),
), ),
Obx(() => SingleChildScrollView( Obx(() => SingleChildScrollView(
controller: controller.uploadScrollController, controller: controller.agentUploadScrollController,
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
...controller.uploadAgentImgFilesPath ...controller.uploadAgentImgFilesPath.map(
.map((String path) => (String path) => builderImagePreview(path, 'agent')),
builderImagePreview(path, 'agent'))
.toList(),
buildImageUploader('agent') buildImageUploader('agent')
], ],
), ),

View File

@ -10,6 +10,7 @@ import 'package:sk_base_mobile/db_helper/dbHelper.dart';
import 'package:sk_base_mobile/models/index.dart'; import 'package:sk_base_mobile/models/index.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/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/snack_bar.util.dart'; import 'package:sk_base_mobile/util/snack_bar.util.dart';
@ -27,7 +28,6 @@ class NewInventoryInoutController extends GetxController {
RxString selectedDate = ''.obs; RxString selectedDate = ''.obs;
RxString startTime = ''.obs; RxString startTime = ''.obs;
RxString endTime = ''.obs; RxString endTime = ''.obs;
RxBool loading = false.obs;
final inventoryInoutController = Get.find<InventoryInoutController>(); final inventoryInoutController = Get.find<InventoryInoutController>();
final label = TextEditingController().obs; final label = TextEditingController().obs;
final description = TextEditingController().obs; final description = TextEditingController().obs;
@ -46,7 +46,8 @@ class NewInventoryInoutController extends GetxController {
final uploadProductImgFilesPath = <String>[].obs; final uploadProductImgFilesPath = <String>[].obs;
final uploadAgentImgFilesPath = <String>[].obs; final uploadAgentImgFilesPath = <String>[].obs;
// final uploadImgFilesPath = <String>[].obs // final uploadImgFilesPath = <String>[].obs
final uploadScrollController = ScrollController(); final productUploadScrollController = ScrollController();
final agentUploadScrollController = ScrollController();
Map<String, dynamic> payload = {}; Map<String, dynamic> payload = {};
NewInventoryInoutController({this.inOrOut}); NewInventoryInoutController({this.inOrOut});
@ -171,7 +172,7 @@ class NewInventoryInoutController extends GetxController {
} }
payload['remark'] = remarkTextController.text; payload['remark'] = remarkTextController.text;
loading.value = true; await LoadingUtil.to.show(status: '提交中请稍后...');
// uploadImgFilesPath // uploadImgFilesPath
try { try {
@ -202,7 +203,7 @@ class NewInventoryInoutController extends GetxController {
} catch (e) { } catch (e) {
LoggerUtil().error(e); LoggerUtil().error(e);
} finally { } finally {
loading.value = false; LoadingUtil.to.dismiss();
} }
} }
@ -212,17 +213,25 @@ class NewInventoryInoutController extends GetxController {
if (pickedFile != null) { if (pickedFile != null) {
if (type == 'product') { if (type == 'product') {
uploadProductImgFilesPath.add(pickedFile.path); uploadProductImgFilesPath.add(pickedFile.path);
Future.delayed(const Duration(milliseconds: 100), () {
//
productUploadScrollController.animateTo(
productUploadScrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 200),
curve: Curves.easeOut,
);
});
} else { } else {
uploadAgentImgFilesPath.add(pickedFile.path); uploadAgentImgFilesPath.add(pickedFile.path);
Future.delayed(const Duration(milliseconds: 100), () {
//
agentUploadScrollController.animateTo(
agentUploadScrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 200),
curve: Curves.easeOut,
);
});
} }
Future.delayed(const Duration(milliseconds: 100), () {
//
uploadScrollController.animateTo(
uploadScrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 200),
curve: Curves.easeOut,
);
});
} }
// await PhotoPickerUtil().showPicker(callback: (XFile pickedFile) async { // await PhotoPickerUtil().showPicker(callback: (XFile pickedFile) async {
// await LoadingUtil.to.show(status: '上传中...'); // await LoadingUtil.to.show(status: '上传中...');
@ -331,7 +340,6 @@ class NewInventoryInoutController extends GetxController {
picEndTime(context); picEndTime(context);
return; return;
} }
loading.value = true;
// db // db
// .insert(TaskModel( // .insert(TaskModel(

View File

@ -43,8 +43,9 @@ class LoadingUtil extends GetxService {
if (status != null) if (status != null)
Text(status, Text(status,
style: TextStyle( style: TextStyle(
decoration: TextDecoration.none,
color: AppTheme.primaryColor, color: AppTheme.primaryColor,
fontSize: ScreenAdaper.height(20))) fontSize: ScreenAdaper.height(25)))
], ],
), ),
) )

View File

@ -9,26 +9,27 @@ class ZtSearchSelect<T> extends StatelessWidget {
final Function? suggestionsCallback; final Function? suggestionsCallback;
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 String hintText;
final EdgeInsetsGeometry? contentPadding;
final bool isRequired; final bool isRequired;
final Widget Function(BuildContext, T) itemBuilder; final Widget Function(BuildContext, T) itemBuilder;
const ZtSearchSelect( const ZtSearchSelect(
{super.key, {super.key,
required this.textController, required this.textController,
required this.labelText,
required this.itemBuilder, required this.itemBuilder,
required this.suggestionsCallback, required this.suggestionsCallback,
this.hintText = '请选择', this.hintText = '请选择',
this.onSelected, this.onSelected,
this.labelText,
this.onClear, this.onClear,
this.contentPadding,
this.isRequired = false}); this.isRequired = false});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return TypeAheadField( return TypeAheadField(
hideOnUnfocus: false, hideOnUnfocus: true,
controller: textController, controller: textController,
suggestionsCallback: (String keyword) { suggestionsCallback: (String keyword) {
return suggestionsCallback != null return suggestionsCallback != null
@ -44,6 +45,7 @@ class ZtSearchSelect<T> extends StatelessWidget {
focusNode: focusNode, focusNode: focusNode,
controller: _, controller: _,
decoration: InputDecoration( decoration: InputDecoration(
contentPadding: contentPadding,
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderSide: const BorderSide( borderSide: const BorderSide(
color: AppTheme.primaryColorLight, width: 2), color: AppTheme.primaryColorLight, width: 2),
@ -59,25 +61,28 @@ class ZtSearchSelect<T> extends StatelessWidget {
} }
}, },
) )
: const SizedBox(), : null,
floatingLabelBehavior: FloatingLabelBehavior.always, floatingLabelBehavior: FloatingLabelBehavior.always,
hintText: hintText, hintText: hintText,
label: Row( label: labelText != null
crossAxisAlignment: CrossAxisAlignment.center, ? Row(
mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center,
children: [ mainAxisSize: MainAxisSize.min,
if (isRequired) children: [
Text( if (isRequired)
"*", Text(
style: TextStyle( "*",
color: Colors.red, style: TextStyle(
fontSize: ScreenAdaper.height(30)), color: Colors.red,
), fontSize: ScreenAdaper.height(30)),
Text( ),
labelText, Text(
style: TextStyle(fontSize: ScreenAdaper.height(30)), labelText ?? '',
), style: TextStyle(
]))); fontSize: ScreenAdaper.height(30)),
),
])
: SizedBox()));
}, },
emptyBuilder: (_) => Container( emptyBuilder: (_) => Container(
alignment: Alignment.center, alignment: Alignment.center,