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

View File

@ -180,11 +180,6 @@ class LoginScreen extends StatelessWidget {
Icons.person_2_outlined,
size: ScreenAdaper.height(40),
),
errorStyle: TextStyle(fontSize: ScreenAdaper.height(20)),
contentPadding: EdgeInsets.symmetric(
vertical: ScreenAdaper.height(10),
horizontal: ScreenAdaper.width(30),
),
hintText: '用户名',
hintStyle: TextStyle(fontSize: ScreenAdaper.height(25)),
border: InputBorder.none,
@ -197,7 +192,6 @@ class LoginScreen extends StatelessWidget {
onChanged: (value) {
_controller.username = value;
},
validator: (value) => value!.isEmpty ? '用户名不能为空' : null,
);
}
@ -225,7 +219,6 @@ class LoginScreen extends StatelessWidget {
_controller.password = value;
},
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/util/debouncer.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';
class InventorySearch extends StatelessWidget {
@ -26,6 +27,10 @@ class InventorySearch extends StatelessWidget {
padding: EdgeInsets.symmetric(
horizontal: ScreenAdaper.width(20),
vertical: ScreenAdaper.height(20)),
child: GestureDetector(
onTap: () {
FocusScope.of(context).unfocus();
},
child: Column(
children: [
buildSearchBar(),
@ -35,7 +40,7 @@ class InventorySearch extends StatelessWidget {
Expanded(child: buildInventoryList())
],
),
);
));
}
Widget buildSearchBar() {
@ -74,13 +79,76 @@ class InventorySearch extends StatelessWidget {
),
))),
SizedBox(
width: ScreenAdaper.width(20),
width: ScreenAdaper.width(5),
),
buildProjectPicker(),
SizedBox(
width: ScreenAdaper.width(5),
),
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() {
return Container(
width: ScreenAdaper.width(200),
@ -134,14 +202,19 @@ class InventorySearch extends StatelessWidget {
: Table(columnWidths: {
0: MinColumnWidth(
FixedColumnWidth(80), FixedColumnWidth(80)),
1: FlexColumnWidth(ScreenAdaper.screenShortDistance() / 4),
2: FlexColumnWidth(ScreenAdaper.screenShortDistance() / 4),
3: MinColumnWidth(
1: MinColumnWidth(
FixedColumnWidth(
ScreenAdaper.screenShortDistance() / 5),
FixedColumnWidth(
ScreenAdaper.screenShortDistance() / 5)),
2: FlexColumnWidth(ScreenAdaper.screenShortDistance() / 4),
3: FlexColumnWidth(ScreenAdaper.screenShortDistance() / 4),
4: MinColumnWidth(
FixedColumnWidth(
ScreenAdaper.screenShortDistance() / 5),
FixedColumnWidth(
ScreenAdaper.screenShortDistance() / 5)),
5: MinColumnWidth(
FixedColumnWidth(
ScreenAdaper.screenShortDistance() / 6),
FixedColumnWidth(
@ -165,6 +238,13 @@ class InventorySearch extends StatelessWidget {
style: listTitleTextStyle,
),
)),
TableCell(
verticalAlignment:
TableCellVerticalAlignment.middle,
child: Text(
'所属项目',
style: listTitleTextStyle,
)),
TableCell(
verticalAlignment:
TableCellVerticalAlignment.middle,
@ -211,6 +291,21 @@ class InventorySearch extends StatelessWidget {
style: textStyle,
),
itemData: itemData),
//
buildTableCell(
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${itemData.project?.name}',
style: textStyle,
),
],
),
itemData: itemData),
//
buildTableCell(
Column(
mainAxisAlignment: MainAxisAlignment.center,
@ -282,6 +377,7 @@ class InventorySearch extends StatelessWidget {
class InventorySearchController extends GetxController {
RxList<InventoryModel> inventories = RxList([]);
final projectTextController = TextEditingController();
RxString searchKey = ''.obs;
final searchBarTextConroller = TextEditingController();
RefreshController refreshController = RefreshController(initialRefresh: true);
@ -289,6 +385,14 @@ class InventorySearchController extends GetxController {
int limit = 15;
int total = 0;
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 {
if (isRefresh == true) {
page = 1;
@ -299,6 +403,7 @@ class InventorySearchController extends GetxController {
'page': page,
'pageSize': 15,
'keyword': searchKey.value,
'projectId': projectId.value == 0 ? null : projectId.value,
'isCreateInout': true,
'isHasInventory': hasInventoryStatus.value,
});
@ -311,6 +416,16 @@ class InventorySearchController extends GetxController {
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 {
await getData(isRefresh: true).then((_) {
refreshController.refreshCompleted(resetFooterState: true);

View File

@ -49,11 +49,10 @@ class NewInventoryInout extends StatelessWidget {
child: Column(
children: [
buildForm(),
Obx(() => GradientButton(
GradientButton(
buttonText: TextEnum.createInventoryInOutBtnText,
onPressed: () => {controller.create()},
isLoading: controller.loading.value,
))
)
],
),
))));
@ -455,15 +454,14 @@ class NewInventoryInout extends StatelessWidget {
height: ScreenAdaper.height(10),
),
Obx(() => SingleChildScrollView(
controller: controller.uploadScrollController,
controller: controller.productUploadScrollController,
scrollDirection: Axis.horizontal,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
...controller.uploadProductImgFilesPath
.map((String path) =>
builderImagePreview(path, 'product'))
.toList(),
...controller.uploadProductImgFilesPath.map(
(String path) =>
builderImagePreview(path, 'product')),
buildImageUploader('product')
],
),
@ -483,15 +481,13 @@ class NewInventoryInout extends StatelessWidget {
height: ScreenAdaper.height(10),
),
Obx(() => SingleChildScrollView(
controller: controller.uploadScrollController,
controller: controller.agentUploadScrollController,
scrollDirection: Axis.horizontal,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
...controller.uploadAgentImgFilesPath
.map((String path) =>
builderImagePreview(path, 'agent'))
.toList(),
...controller.uploadAgentImgFilesPath.map(
(String path) => builderImagePreview(path, '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/screens/inventory_inout/inventory_inout_controller.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/media_util.dart';
import 'package:sk_base_mobile/util/snack_bar.util.dart';
@ -27,7 +28,6 @@ class NewInventoryInoutController extends GetxController {
RxString selectedDate = ''.obs;
RxString startTime = ''.obs;
RxString endTime = ''.obs;
RxBool loading = false.obs;
final inventoryInoutController = Get.find<InventoryInoutController>();
final label = TextEditingController().obs;
final description = TextEditingController().obs;
@ -46,7 +46,8 @@ class NewInventoryInoutController extends GetxController {
final uploadProductImgFilesPath = <String>[].obs;
final uploadAgentImgFilesPath = <String>[].obs;
// final uploadImgFilesPath = <String>[].obs
final uploadScrollController = ScrollController();
final productUploadScrollController = ScrollController();
final agentUploadScrollController = ScrollController();
Map<String, dynamic> payload = {};
NewInventoryInoutController({this.inOrOut});
@ -171,7 +172,7 @@ class NewInventoryInoutController extends GetxController {
}
payload['remark'] = remarkTextController.text;
loading.value = true;
await LoadingUtil.to.show(status: '提交中请稍后...');
// uploadImgFilesPath
try {
@ -202,7 +203,7 @@ class NewInventoryInoutController extends GetxController {
} catch (e) {
LoggerUtil().error(e);
} finally {
loading.value = false;
LoadingUtil.to.dismiss();
}
}
@ -212,17 +213,25 @@ class NewInventoryInoutController extends GetxController {
if (pickedFile != null) {
if (type == 'product') {
uploadProductImgFilesPath.add(pickedFile.path);
} else {
uploadAgentImgFilesPath.add(pickedFile.path);
}
Future.delayed(const Duration(milliseconds: 100), () {
//
uploadScrollController.animateTo(
uploadScrollController.position.maxScrollExtent,
productUploadScrollController.animateTo(
productUploadScrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 200),
curve: Curves.easeOut,
);
});
} else {
uploadAgentImgFilesPath.add(pickedFile.path);
Future.delayed(const Duration(milliseconds: 100), () {
//
agentUploadScrollController.animateTo(
agentUploadScrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 200),
curve: Curves.easeOut,
);
});
}
}
// await PhotoPickerUtil().showPicker(callback: (XFile pickedFile) async {
// await LoadingUtil.to.show(status: '上传中...');
@ -331,7 +340,6 @@ class NewInventoryInoutController extends GetxController {
picEndTime(context);
return;
}
loading.value = true;
// db
// .insert(TaskModel(

View File

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