feat: product searcher

This commit is contained in:
louis 2024-03-25 14:16:00 +08:00
parent 9e39aa8c41
commit 7986fde5d3
28 changed files with 858 additions and 86 deletions

View File

@ -3,6 +3,7 @@ import 'dart:io';
import 'package:dio/dio.dart';
import 'package:sk_base_mobile/constants/global_url.dart';
import 'package:sk_base_mobile/models/base_response.dart';
import 'package:sk_base_mobile/models/dict_type.model.dart';
import 'package:sk_base_mobile/services/dio.service.dart';
import '../constants/constants.dart';
@ -39,6 +40,11 @@ Future<Response<PaginationData>> getInventoryInout(Map params) {
queryParameters: {'page': 1, 'pageSize': 10, ...(params)});
}
// ()
Future<Response> getDictTypeAll(Map params) {
return DioService.dio.post(Urls.getDictType, data: params);
}
Future<Response> logout() {
return DioService.dio.post(
Urls.logout,

View File

@ -0,0 +1,5 @@
class DictTypeEnum {
static const String InventoryRoom = "inventory_room";
static const String InventoryLine = 'inventory_line';
static const String InventoryLineLevel = 'inventory_line_level';
}

View File

@ -10,4 +10,5 @@ class Urls {
static String getProducts = 'product';
static String getInventoryInout = 'materials-in-out';
static String updateAvatar = 'user/updateAvatar';
static String getDictType = 'system/dict-type/all';
}

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:sk_base_mobile/store/dict.store.dart';
import 'package:sk_base_mobile/util/loading_util.dart';
import 'package:timeago/timeago.dart' as timeago;
import 'package:sk_base_mobile/constants/cache_key.dart';
@ -42,6 +43,9 @@ class Global {
///
await Get.putAsync<AuthStore>(() => AuthStore().init());
///
await Get.putAsync<DictService>(() => DictService().init());
if (StorageService.to.getString(CacheKeys.token, isWithUser: false) !=
null) {
///

View File

@ -5,7 +5,7 @@ import 'package:sk_base_mobile/config.dart';
import 'package:sk_base_mobile/services/storage.service.dart';
import 'package:sk_base_mobile/app_theme.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
import 'package:sk_base_mobile/widgets/refresh-footer.dart';
import 'package:sk_base_mobile/widgets/refresh_footer.dart';
import 'package:sk_base_mobile/widgets/refresh_header.dart';
import 'constants/constants.dart';

View File

@ -1,15 +1,15 @@
class DictItemModel {
DictItemModel({
required this.id,
required this.createdAt,
required this.updatedAt,
required this.creator,
required this.updater,
this.id,
this.createdAt,
this.updatedAt,
this.creator,
this.updater,
required this.label,
required this.value,
required this.orderNo,
required this.status,
required this.remark,
this.value,
this.orderNo,
this.status,
this.remark,
});
final int? id;
@ -17,7 +17,7 @@ class DictItemModel {
final DateTime? updatedAt;
final String? creator;
final String? updater;
final String? label;
final String label;
final String? value;
final int? orderNo;
final int? status;

View File

@ -0,0 +1,58 @@
import 'dict_item.model.dart';
class DictTypeModel {
DictTypeModel({
required this.id,
required this.createdAt,
required this.updatedAt,
required this.creator,
required this.updater,
required this.name,
required this.code,
required this.status,
required this.remark,
required this.dictItems,
});
final int? id;
final DateTime? createdAt;
final DateTime? updatedAt;
final String? creator;
final String? updater;
final String? name;
final String? code;
final int? status;
final String? remark;
final List<DictItemModel> dictItems;
factory DictTypeModel.fromJson(Map<String, dynamic> json) {
return DictTypeModel(
id: json["id"],
createdAt: DateTime.tryParse(json["createdAt"] ?? ""),
updatedAt: DateTime.tryParse(json["updatedAt"] ?? ""),
creator: json["creator"],
updater: json["updater"],
name: json["name"],
code: json["code"],
status: json["status"],
remark: json["remark"],
dictItems: json["dictItems"] == null
? []
: List<DictItemModel>.from(
json["dictItems"]!.map((x) => DictItemModel.fromJson(x))),
);
}
Map<String, dynamic> toJson() => {
"id": id,
"createdAt": createdAt?.toIso8601String(),
"updatedAt": updatedAt?.toIso8601String(),
"creator": creator,
"updater": updater,
"name": name,
"code": code,
"status": status,
"remark": remark,
"dictItems": dictItems.map((x) => x.toJson()).toList(),
};
}

View File

@ -4,22 +4,25 @@ import 'package:sk_base_mobile/models/file.model.dart';
import 'company.model.dart';
class ProductModel {
ProductModel({
required this.id,
required this.createdAt,
required this.updatedAt,
required this.name,
required this.remark,
required this.isDelete,
required this.companyId,
required this.unitId,
required this.namePinyin,
required this.files,
required this.company,
required this.unit,
});
ProductModel(
{required this.id,
required this.createdAt,
required this.updatedAt,
required this.name,
required this.remark,
required this.isDelete,
required this.companyId,
required this.unitId,
required this.namePinyin,
required this.files,
required this.company,
required this.unit,
this.productNumber,
this.productSpecification});
final int? id;
final String? productNumber;
final String? productSpecification;
final DateTime? createdAt;
final DateTime? updatedAt;
final String? name;
@ -41,6 +44,8 @@ class ProductModel {
remark: json["remark"],
isDelete: json["isDelete"],
companyId: json["companyId"],
productNumber: json['productNumber'],
productSpecification: json['productSpecification'],
unitId: json["unitId"],
namePinyin: json["namePinyin"],
files: json["files"] == null
@ -63,6 +68,8 @@ class ProductModel {
"isDelete": isDelete,
"companyId": companyId,
"unitId": unitId,
'productNumber': productNumber,
'productSpecification': productSpecification,
"namePinyin": namePinyin,
"files": files.map((x) => x?.toJson()).toList(),
"company": company?.toJson(),

View File

@ -3,14 +3,17 @@ import 'package:get/get.dart';
import 'package:sk_base_mobile/screens/inventory_inout/components/task_list.dart';
import 'package:sk_base_mobile/screens/inventory_inout/inventory_inout_controller.dart';
class TaskPageView extends StatelessWidget {
TaskPageView({super.key});
class InventoryInoutView extends StatelessWidget {
InventoryInoutView({super.key});
final controller = Get.put(InventoryInoutController());
@override
Widget build(BuildContext context) {
return PageView(
physics: const BouncingScrollPhysics(),
reverse: true,
onPageChanged: (index) {
controller.changePage(index);
},
controller: controller.pageController,
children: List.generate(
controller.daysNum,

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:sk_base_mobile/screens/inventory_inout/components/change_button_roe.dart';
import 'package:sk_base_mobile/screens/inventory_inout/components/task_page_View.dart';
import 'package:sk_base_mobile/screens/inventory_inout/components/inventory_inout_page_View.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
class TaskPageBody extends StatelessWidget {
@ -38,7 +38,7 @@ class TaskPageBody extends StatelessWidget {
Colors.white.withOpacity(.0),
Colors.white.withOpacity(.0),
])),
child: TaskPageView(),
child: InventoryInoutView(),
));
}
}

View File

@ -4,6 +4,7 @@ import 'package:sk_base_mobile/app_theme.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/util/date.util.dart';
import 'package:sk_base_mobile/util/logger_util.dart';
import 'package:sk_base_mobile/util/modal.util.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
import 'package:sk_base_mobile/apis/api.dart' as Api;
@ -36,6 +37,12 @@ class InventoryInoutController extends GetxController {
// getInoutHistory();
}
Future<void> changePage(int index) async {
LoggerUtil()
.info(DateUtil.format(DateTime.now().add(Duration(days: -index))));
}
///
Future<void> showInOrOutPickerDialog() async {
showDialog(

View File

@ -0,0 +1,149 @@
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/models/index.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 ProductSearch extends StatelessWidget {
ProductSearch({super.key});
final controller = Get.put(ProductSearchController());
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.symmetric(
horizontal: ScreenAdaper.width(20),
vertical: ScreenAdaper.height(20)),
child: Column(
children: [buildSearchBar(), Expanded(child: buildProductList())],
),
);
}
Widget buildSearchBar() {
final doSearch = debouncer((_) {
controller.onRefresh();
}, delayTime: 500);
return Row(
children: [
Expanded(
child: TextField(
controller: controller.searchBarController,
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),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
))),
],
);
}
Widget buildProductList() {
return Obx(
() => SmartRefresher(
enablePullDown: true,
enablePullUp: true,
controller: controller.refreshController,
onLoading: controller.onLoading,
onRefresh: controller.onRefresh,
child: ListView.separated(
itemCount: controller.products.length,
separatorBuilder: (context, index) => const Divider(
height: 1,
color: AppTheme.dividerColor,
),
itemBuilder: (context, index) {
final itemData = controller.products[index];
return InkWell(
onTap: () {},
child: Container(
padding:
EdgeInsets.symmetric(vertical: ScreenAdaper.height(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),
)
],
)),
],
),
),
);
}),
),
);
}
}
class ProductSearchController extends GetxController {
RxList<ProductModel> products = RxList([]);
TextEditingController searchBarController = TextEditingController(text: '');
RefreshController refreshController = RefreshController(initialRefresh: true);
int page = 1;
int limit = 15;
int total = 0;
Future<List<ProductModel>> getData({bool isRefresh = false}) async {
if (isRefresh == true) {
page = 1;
} else {
page++;
}
final res = await Api.getProducts(
{'page': page, 'pageSize': 30, 'keyword': searchBarController.text});
List<ProductModel> newList =
res.data!.items.map((e) => ProductModel.fromJson(e)).toList();
isRefresh == true ? products.assignAll(newList) : products.addAll(newList);
return newList;
}
Future<void> onRefresh() async {
await getData(isRefresh: true).then((_) {
refreshController.refreshCompleted(resetFooterState: true);
}).catchError((_) {
refreshController.refreshFailed();
});
}
Future<void> onLoading() async {
await getData().then((_) {
if (_.isEmpty) {
refreshController.loadNoData();
} else {
refreshController.loadComplete();
}
}).catchError((_) {
refreshController.loadFailed();
});
}
}

View File

@ -1,17 +1,24 @@
import 'dart:io';
import 'package:dropdown_search/dropdown_search.dart';
import 'package:flutter/material.dart';
import 'package:flutter_typeahead/flutter_typeahead.dart';
import 'package:get/get.dart';
import 'package:sk_base_mobile/app_theme.dart';
import 'package:sk_base_mobile/constants/constants.dart';
import 'package:sk_base_mobile/constants/dict_enum.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/screens/new_inventory_inout/components/product_search.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/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_search_select.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/empty.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/util/screen_adaper_util.dart';
@ -21,7 +28,7 @@ class NewInventoryInout extends StatelessWidget {
NewInventoryInout({super.key, this.inOrOut = 'in'});
final controller = Get.put(NewInventoryInoutController());
final dictService = Get.find<DictService>();
@override
Widget build(BuildContext context) {
return SafeArea(
@ -88,12 +95,13 @@ class NewInventoryInout extends StatelessWidget {
SizedBox(
height: ScreenAdaper.height(formVerticalGap),
),
Row(
children: [Expanded(child: buildRemark())],
),
buildPositionBottomPicker(),
SizedBox(
height: ScreenAdaper.height(formVerticalGap),
),
Row(
children: [Expanded(child: buildRemark())],
),
buildImageUploadQueue()
];
@ -116,16 +124,16 @@ class NewInventoryInout extends StatelessWidget {
///
Widget buildProjectPicker() {
return ZtSearchSelect(
return ZtSearchSelect<ProjectModel>(
isRequired: true,
textController: controller.projectTextController,
labelText: '项目',
itemBuilder: (itemData) => Container(
itemBuilder: (_, itemData) => Container(
padding: EdgeInsets.symmetric(
vertical: ScreenAdaper.height(15),
horizontal: ScreenAdaper.width(20)),
child: Text(
'${itemData?.name}',
'${itemData.name}',
style: TextStyle(fontSize: ScreenAdaper.sp(20)),
)),
suggestionsCallback: (String keyword) {
@ -142,24 +150,168 @@ class NewInventoryInout extends StatelessWidget {
///
Widget buildProductPicker() {
return ZtSearchSelect(
isRequired: true,
textController: controller.productTextController,
labelText: '产品',
itemBuilder: (itemData) => ListTile(
title: Text('${itemData?.name}'),
subtitle: Text('${itemData?.company?.name ?? ''}'),
),
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;
return TextFormField(
controller: controller.positionTextController,
decoration: const InputDecoration(
labelText: '产品',
floatingLabelBehavior: FloatingLabelBehavior.always),
readOnly: true,
onTap: () {
Get.generalDialog(
barrierLabel: "productPicker",
barrierDismissible: true,
transitionDuration: const Duration(milliseconds: 400),
pageBuilder: (_, __, ___) {
return Center(
child: ClipRRect(
borderRadius: BorderRadius.circular(30),
child: Material(
child: ClipRRect(
borderRadius: const BorderRadius.only(
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;
tween = Tween(begin: const Offset(0, 1), end: Offset.zero);
return SlideTransition(
position: tween.animate(
CurvedAnimation(parent: anim, curve: Curves.easeInOut),
),
child: child,
);
},
);
});
// 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;
// });
}
///
@ -205,6 +357,73 @@ class NewInventoryInout extends StatelessWidget {
);
}
///
Widget buildPositionBottomPicker() {
final firstLevelData = [
...dictService.getDictItemsByCode(DictTypeEnum.InventoryRoom)
];
final secondLevelData = [
DictItemModel(label: '未知', value: '未知'),
...dictService.getDictItemsByCode(DictTypeEnum.InventoryLine)
];
final thirdDatas =
dictService.getDictItemsByCode(DictTypeEnum.InventoryLineLevel);
final thirdtLevelData = [
if (thirdDatas.isNotEmpty) DictItemModel(label: '未知', value: '未知'),
...thirdDatas
];
return TextFormField(
controller: controller.positionTextController,
decoration: const InputDecoration(
labelText: '库存位置',
floatingLabelBehavior: FloatingLabelBehavior.always),
readOnly: true,
onTap: () {
printInfo(
info: firstLevelData
.map((e) => Center(child: (Text(e.label))))
.toList()
.toString());
ModalUtil.showBottomSheetPicker(
title: '库存位置',
firstLevel: firstLevelData
.map((e) => Center(child: (Text(e.label))))
.toList(),
secondLevel: secondLevelData
.map((e) => Center(child: (Text(e.label))))
.toList(),
thirdLevel: thirdtLevelData
.map((e) => Center(child: (Text(e.label))))
.toList(),
onChanged: (_0, _1, _2) {
String position = firstLevelData[_0].label;
if (_1 >= 1) {
position += ', ${secondLevelData[_1].label}';
}
if (_2 >= 1) {
position += ', ${thirdtLevelData[_2].label}';
}
// , ${secondLevelData[_1].label}, ${thirdtLevelData[_2].label}
controller.positionTextController.text = 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: () {}),
// );
}
///
Widget buildImageUploadQueue() {
return Container(
@ -217,7 +436,7 @@ class NewInventoryInout extends StatelessWidget {
Text(
'*请拍照上传产品照片',
style: TextStyle(
fontSize: ScreenAdaper.sp(30),
fontSize: ScreenAdaper.sp(25),
color: AppTheme.secondPrimaryColor),
),
SizedBox(
@ -247,8 +466,8 @@ class NewInventoryInout extends StatelessWidget {
children: [
Container(
margin: EdgeInsets.symmetric(horizontal: ScreenAdaper.width(5)),
width: ScreenAdaper.width(200),
height: ScreenAdaper.width(200),
width: ScreenAdaper.width(180),
height: ScreenAdaper.width(180),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
border: Border.all(width: 1.0, color: AppTheme.dividerColor),
@ -303,8 +522,8 @@ class NewInventoryInout extends StatelessWidget {
},
child: Container(
margin: EdgeInsets.symmetric(horizontal: ScreenAdaper.width(5)),
width: ScreenAdaper.width(200),
height: ScreenAdaper.width(200),
width: ScreenAdaper.width(180),
height: ScreenAdaper.width(180),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
border: Border.all(width: 1.0, color: AppTheme.dividerColor),

View File

@ -44,6 +44,7 @@ class NewInventoryInoutController extends GetxController {
final quantityTextController = TextEditingController();
final agentTextController = TextEditingController();
final remarkTextController = TextEditingController();
final positionTextController = TextEditingController();
final uploadImgFilesPath = <String>[].obs;
final uploadScrollController = ScrollController();
Map<String, dynamic> payload = {};
@ -60,7 +61,7 @@ class NewInventoryInoutController extends GetxController {
Future<List<ProductModel>> getProducts({String? keyword}) async {
final res =
await Api.getProducts({'page': 1, 'pageSize': 10, 'name': keyword});
await Api.getProducts({'page': 1, 'pageSize': 10, 'keyword': keyword});
if (res.data != null) {
products.assignAll(
res.data!.items.map((e) => ProductModel.fromJson(e)).toList());

View File

@ -5,7 +5,6 @@ import 'package:get/get.dart';
import 'package:sk_base_mobile/apis/api.dart' as Api;
import 'package:sk_base_mobile/util/logger_util.dart';
import 'package:sk_base_mobile/widgets/tap_to_dismiss_keyboard.dart';
import 'package:sk_base_mobile/config.dart';
import 'package:sk_base_mobile/models/auth.dart';
import 'package:sk_base_mobile/models/user_info.model.dart';
import 'package:sk_base_mobile/services/service.dart';

40
lib/store/dict.store.dart Normal file
View File

@ -0,0 +1,40 @@
import 'package:get/get.dart';
import 'package:get/get_state_manager/get_state_manager.dart';
import 'package:sk_base_mobile/constants/dict_enum.dart';
import 'package:sk_base_mobile/models/dict_item.model.dart';
import 'package:sk_base_mobile/models/dict_type.model.dart';
import 'package:sk_base_mobile/apis/api.dart' as Api;
import 'package:sk_base_mobile/util/logger_util.dart';
const needCachedKey = [
DictTypeEnum.InventoryRoom,
DictTypeEnum.InventoryLine,
DictTypeEnum.InventoryLineLevel
];
class DictService extends GetxService {
Future<DictService> init() async {
await getDictTypes();
return this;
}
RxList<DictTypeModel> dictTypes = RxList([]);
Future<void> getDictTypes() async {
try {
final response = await Api.getDictTypeAll({
'storeCodes': needCachedKey,
'withItems': true,
});
dictTypes.value = (response.data as List)
.map((item) => DictTypeModel.fromJson(item))
.toList();
} catch (e) {
LoggerUtil().error('getDictTypes error: $e');
}
}
List<DictItemModel> getDictItemsByCode(String code) {
return dictTypes.firstWhereOrNull((item) => item.code == code)?.dictItems ??
[];
}
}

View File

@ -1,6 +1,8 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:get/get.dart';
import 'package:sk_base_mobile/widgets/core/zt_bottomsheet_picker.dart';
class ModalUtil {
static Future<bool> showWarningDialog(
@ -40,4 +42,27 @@ class ModalUtil {
);
return confirmed;
}
/// 3
static Future<void> showBottomSheetPicker(
{title = '请选择',
required onChanged,
required firstLevel,
secondLevel,
thirdLevel,
itemHeight,
popupHeight}) async =>
showCupertinoModalPopup(
context: Get.overlayContext!,
builder: (BuildContext context) {
return ZtBottomSheetPicker(
title: title,
onChanged: onChanged,
firstLevel: firstLevel,
secondLevel: secondLevel,
thirdLevel: thirdLevel,
itemHeight: itemHeight ?? 80.0,
popupHeight: popupHeight ?? 400);
},
);
}

View File

@ -0,0 +1,101 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:sk_base_mobile/util/logger_util.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
class ZtBaseDatePicker extends StatelessWidget {
final Function? onDateTimeChanged;
final yearController =
FixedExtentScrollController(initialItem: DateTime.now().year - 2000);
final monthController =
FixedExtentScrollController(initialItem: DateTime.now().month - 1);
final dayController =
FixedExtentScrollController(initialItem: DateTime.now().day - 1);
ZtBaseDatePicker({super.key, this.onDateTimeChanged});
@override
Widget build(BuildContext context) {
return Container(
height: ScreenAdaper.height(250),
color: Colors.white,
child: Column(
children: [
Row(
children: [
TextButton(
onPressed: () {
Get.back();
},
child: Text(
'取消',
style: TextStyle(fontSize: ScreenAdaper.sp(25)),
)),
const Spacer(),
TextButton(
onPressed: () {
Get.back();
final year = 2000 + yearController.selectedItem;
String month = '${monthController.selectedItem + 1}';
if (int.parse(month) < 10) {
month = '0$month';
}
final day = dayController.selectedItem + 1;
if (onDateTimeChanged != null) {
onDateTimeChanged!('$year-$month-$day');
}
LoggerUtil().info('$year-$month-$day');
},
child: Text(
'确定',
style: TextStyle(fontSize: ScreenAdaper.sp(25)),
))
],
),
Expanded(
child: Row(
children: <Widget>[
Expanded(
child: CupertinoPicker(
scrollController: yearController,
itemExtent: ScreenAdaper.height(60),
onSelectedItemChanged: (int index) {
//
},
children: List<Widget>.generate(DateTime.now().year - 1999,
(int index) {
return Center(child: Text('${2000 + index}'));
}),
),
),
Expanded(
child: CupertinoPicker(
scrollController: monthController,
itemExtent: ScreenAdaper.height(60),
onSelectedItemChanged: (int index) {
//
},
children: List<Widget>.generate(12, (int index) {
return Center(child: Text('${index + 1}'));
}),
),
),
Expanded(
child: CupertinoPicker(
scrollController: dayController,
itemExtent: ScreenAdaper.height(60),
onSelectedItemChanged: (int index) {
//
},
children: List<Widget>.generate(31, (int index) {
return Center(child: Text('${index + 1}'));
}),
),
),
],
))
],
),
);
}
}

View File

@ -0,0 +1,112 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:sk_base_mobile/app_theme.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
class ZtBottomSheetPicker extends StatelessWidget {
final Function? onChanged;
FixedExtentScrollController firstLevelControlller =
FixedExtentScrollController();
FixedExtentScrollController secondLevelController =
FixedExtentScrollController();
FixedExtentScrollController thirdLevelController =
FixedExtentScrollController();
final List<Widget> firstLevel;
final List<Widget>? secondLevel;
final List<Widget>? thirdLevel;
double? itemHeight;
double? popupHeight;
final String title;
ZtBottomSheetPicker(
{super.key,
this.title = '请选择',
required this.onChanged,
required this.firstLevel,
this.secondLevel,
this.thirdLevel,
this.itemHeight = 80.0,
this.popupHeight = 400.0});
@override
Widget build(BuildContext context) {
return Container(
height: ScreenAdaper.height(popupHeight!),
color: Colors.white,
child: Column(
children: [
Row(
children: [
TextButton(
onPressed: () {
Get.back();
},
child: Text(
'取消',
style: TextStyle(fontSize: ScreenAdaper.sp(25)),
)),
Expanded(
child: Center(
child: Text(
title,
style: TextStyle(
color: AppTheme.nearlyBlack,
fontSize: ScreenAdaper.sp(25),
decoration: TextDecoration.none),
),
)),
TextButton(
onPressed: () {
Get.back();
onChanged!(
firstLevelControlller.selectedItem,
(secondLevel ?? []).isNotEmpty
? secondLevelController.selectedItem
: -1,
(thirdLevel ?? []).isNotEmpty
? thirdLevelController.selectedItem
: -1,
);
},
child: Text(
'确定',
style: TextStyle(fontSize: ScreenAdaper.sp(25)),
))
],
),
Expanded(
child: Row(
children: <Widget>[
Expanded(
child: CupertinoPicker(
scrollController: firstLevelControlller,
itemExtent: ScreenAdaper.height(itemHeight!),
onSelectedItemChanged: (int index) {},
children: firstLevel,
),
),
if ((secondLevel ?? []).isNotEmpty)
Expanded(
child: CupertinoPicker(
scrollController: secondLevelController,
itemExtent: ScreenAdaper.height(itemHeight!),
onSelectedItemChanged: (int index) {},
children: secondLevel!,
),
),
if ((thirdLevel ?? []).isNotEmpty)
Expanded(
child: CupertinoPicker(
scrollController: thirdLevelController,
itemExtent: ScreenAdaper.height(itemHeight!),
onSelectedItemChanged: (int index) {},
children: thirdLevel!,
),
),
],
))
],
),
);
}
}

View File

@ -4,14 +4,14 @@ import 'package:get/get.dart';
import 'package:sk_base_mobile/app_theme.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
class ZtSearchSelect extends StatelessWidget {
class ZtSearchSelect<T> extends StatelessWidget {
final TextEditingController textController;
final Function? suggestionsCallback;
final Function? onSelected;
final void Function(T)? onSelected;
final VoidCallback? onClear;
final String labelText;
final bool isRequired;
final Widget Function(dynamic itemData)? itemBuilder;
final Widget Function(BuildContext, T) itemBuilder;
const ZtSearchSelect(
{super.key,
required this.textController,
@ -89,13 +89,13 @@ class ZtSearchSelect extends StatelessWidget {
),
),
itemBuilder: (context, itemData) {
return itemBuilder!(itemData);
return itemBuilder(context, itemData as T);
},
onSelected: (selectedItemData) {
//
FocusScope.of(context).unfocus();
if (onSelected != null) {
onSelected!(selectedItemData);
onSelected!(selectedItemData as T);
}
});
}

View File

@ -12,14 +12,15 @@ class ZtTextInput extends StatelessWidget {
final String labelText;
final String? hint;
final bool isTextArea;
const ZtTextInput(
{super.key,
required this.textController,
this.onTap,
this.hint,
this.isRequired = false,
this.labelText = '',
this.isTextArea = false});
const ZtTextInput({
super.key,
required this.textController,
this.onTap,
this.hint,
this.isRequired = false,
this.labelText = '',
this.isTextArea = false,
});
@override
Widget build(BuildContext context) {
return TextFormField(
@ -27,7 +28,7 @@ class ZtTextInput extends StatelessWidget {
onTapOutside: (event) {
FocusScope.of(context).unfocus();
},
maxLines: isTextArea ? 3 : 1, //
maxLines: isTextArea ? 2 : 1, //
onTap: onTap ?? () {},
decoration: InputDecoration(
floatingLabelBehavior: FloatingLabelBehavior.always,

View File

@ -10,10 +10,10 @@ class Empty extends StatelessWidget {
Widget build(BuildContext context) {
return Center(
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
Center(
child: Image(
height: ScreenAdaper.height(130),
image: const AssetImage('assets/images/empty_icon.png'))),
// Center(
// child: Image(
// height: ScreenAdaper.height(130),
// image: const AssetImage('assets/images/empty_icon.png'))),
Text(
text ?? '',
textAlign: TextAlign.center,

View File

@ -0,0 +1,17 @@
import 'package:flutter/cupertino.dart';
import 'package:sk_base_mobile/app_theme.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
class LoadingIndicator extends StatelessWidget {
final bool animating;
const LoadingIndicator({super.key, this.animating = true});
@override
Widget build(BuildContext context) {
return CupertinoActivityIndicator(
animating: animating,
color: AppTheme.primaryColor,
radius: ScreenAdaper.sp(20),
);
}
}

View File

@ -1,6 +1,9 @@
import 'package:flutter/cupertino.dart';
import 'package:loading_animation_widget/loading_animation_widget.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
import 'package:sk_base_mobile/app_theme.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
import 'package:sk_base_mobile/widgets/loading_indicator.dart';
class RefreshFooter extends StatefulWidget {
const RefreshFooter({Key? key}) : super(key: key);
@ -32,16 +35,16 @@ class _RefreshFooterState extends State<RefreshFooter>
builder: (BuildContext context, LoadStatus? status) {
Widget body;
if (status == LoadStatus.idle) {
body = const Text("Pull up to get latest messages");
body = const Text("上拉获取更多");
} else if (status == LoadStatus.loading) {
body = const SizedBox(child: const CupertinoActivityIndicator());
body = const LoadingIndicator();
} else if (status == LoadStatus.failed) {
body = Text(
"Load failed, please try again",
"获取失败,请重试",
style: style,
);
} else if (status == LoadStatus.canLoading) {
body = const CupertinoActivityIndicator(
body = const LoadingIndicator(
animating: false,
);
} else {

View File

@ -1,6 +1,9 @@
import 'package:flutter/cupertino.dart';
import 'package:loading_animation_widget/loading_animation_widget.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
import 'package:sk_base_mobile/app_theme.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
import 'package:sk_base_mobile/widgets/loading_indicator.dart';
class RefreshHeader extends RefreshIndicator {
RefreshHeader()
@ -39,15 +42,17 @@ class RefreshHeaderState extends RefreshIndicatorState<RefreshHeader>
TextStyle style = TextStyle(fontSize: ScreenAdaper.sp(18));
Widget body;
if (mode == RefreshStatus.refreshing) {
body = const CupertinoActivityIndicator();
body = const LoadingIndicator();
} else if (mode == RefreshStatus.canRefresh) {
body = const CupertinoActivityIndicator(animating: false);
body = const LoadingIndicator(
animating: false,
);
} else if (mode == RefreshStatus.completed) {
body = const SizedBox();
} else if (mode == RefreshStatus.failed) {
body = Text("No more Data)", style: style);
body = Text("没有更多数据了", style: style);
} else {
body = const CupertinoActivityIndicator();
body = const LoadingIndicator();
}
return SizedBox(
height: ScreenAdaper.height(40),

View File

@ -177,6 +177,14 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:

View File

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