feat: product searcher
This commit is contained in:
parent
9e39aa8c41
commit
7986fde5d3
|
@ -3,6 +3,7 @@ import 'dart:io';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:sk_base_mobile/constants/global_url.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/base_response.dart';
|
||||||
|
import 'package:sk_base_mobile/models/dict_type.model.dart';
|
||||||
import 'package:sk_base_mobile/services/dio.service.dart';
|
import 'package:sk_base_mobile/services/dio.service.dart';
|
||||||
import '../constants/constants.dart';
|
import '../constants/constants.dart';
|
||||||
|
|
||||||
|
@ -39,6 +40,11 @@ Future<Response<PaginationData>> getInventoryInout(Map params) {
|
||||||
queryParameters: {'page': 1, 'pageSize': 10, ...(params)});
|
queryParameters: {'page': 1, 'pageSize': 10, ...(params)});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 一次性获取所有的字典类型(不分页)
|
||||||
|
Future<Response> getDictTypeAll(Map params) {
|
||||||
|
return DioService.dio.post(Urls.getDictType, data: params);
|
||||||
|
}
|
||||||
|
|
||||||
Future<Response> logout() {
|
Future<Response> logout() {
|
||||||
return DioService.dio.post(
|
return DioService.dio.post(
|
||||||
Urls.logout,
|
Urls.logout,
|
||||||
|
|
|
@ -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';
|
||||||
|
}
|
|
@ -10,4 +10,5 @@ class Urls {
|
||||||
static String getProducts = 'product';
|
static String getProducts = 'product';
|
||||||
static String getInventoryInout = 'materials-in-out';
|
static String getInventoryInout = 'materials-in-out';
|
||||||
static String updateAvatar = 'user/updateAvatar';
|
static String updateAvatar = 'user/updateAvatar';
|
||||||
|
static String getDictType = 'system/dict-type/all';
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:get/get.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:sk_base_mobile/util/loading_util.dart';
|
||||||
import 'package:timeago/timeago.dart' as timeago;
|
import 'package:timeago/timeago.dart' as timeago;
|
||||||
import 'package:sk_base_mobile/constants/cache_key.dart';
|
import 'package:sk_base_mobile/constants/cache_key.dart';
|
||||||
|
@ -42,6 +43,9 @@ class Global {
|
||||||
/// 依赖注入用户信息
|
/// 依赖注入用户信息
|
||||||
await Get.putAsync<AuthStore>(() => AuthStore().init());
|
await Get.putAsync<AuthStore>(() => AuthStore().init());
|
||||||
|
|
||||||
|
/// 依赖注入字典信息
|
||||||
|
await Get.putAsync<DictService>(() => DictService().init());
|
||||||
|
|
||||||
if (StorageService.to.getString(CacheKeys.token, isWithUser: false) !=
|
if (StorageService.to.getString(CacheKeys.token, isWithUser: false) !=
|
||||||
null) {
|
null) {
|
||||||
/// 这里做一些缓存初始化后的操作
|
/// 这里做一些缓存初始化后的操作
|
||||||
|
|
|
@ -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/services/storage.service.dart';
|
||||||
import 'package:sk_base_mobile/app_theme.dart';
|
import 'package:sk_base_mobile/app_theme.dart';
|
||||||
import 'package:pull_to_refresh/pull_to_refresh.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 'package:sk_base_mobile/widgets/refresh_header.dart';
|
||||||
|
|
||||||
import 'constants/constants.dart';
|
import 'constants/constants.dart';
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
class DictItemModel {
|
class DictItemModel {
|
||||||
DictItemModel({
|
DictItemModel({
|
||||||
required this.id,
|
this.id,
|
||||||
required this.createdAt,
|
this.createdAt,
|
||||||
required this.updatedAt,
|
this.updatedAt,
|
||||||
required this.creator,
|
this.creator,
|
||||||
required this.updater,
|
this.updater,
|
||||||
required this.label,
|
required this.label,
|
||||||
required this.value,
|
this.value,
|
||||||
required this.orderNo,
|
this.orderNo,
|
||||||
required this.status,
|
this.status,
|
||||||
required this.remark,
|
this.remark,
|
||||||
});
|
});
|
||||||
|
|
||||||
final int? id;
|
final int? id;
|
||||||
|
@ -17,7 +17,7 @@ class DictItemModel {
|
||||||
final DateTime? updatedAt;
|
final DateTime? updatedAt;
|
||||||
final String? creator;
|
final String? creator;
|
||||||
final String? updater;
|
final String? updater;
|
||||||
final String? label;
|
final String label;
|
||||||
final String? value;
|
final String? value;
|
||||||
final int? orderNo;
|
final int? orderNo;
|
||||||
final int? status;
|
final int? status;
|
||||||
|
|
|
@ -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(),
|
||||||
|
};
|
||||||
|
}
|
|
@ -4,8 +4,8 @@ import 'package:sk_base_mobile/models/file.model.dart';
|
||||||
import 'company.model.dart';
|
import 'company.model.dart';
|
||||||
|
|
||||||
class ProductModel {
|
class ProductModel {
|
||||||
ProductModel({
|
ProductModel(
|
||||||
required this.id,
|
{required this.id,
|
||||||
required this.createdAt,
|
required this.createdAt,
|
||||||
required this.updatedAt,
|
required this.updatedAt,
|
||||||
required this.name,
|
required this.name,
|
||||||
|
@ -17,9 +17,12 @@ class ProductModel {
|
||||||
required this.files,
|
required this.files,
|
||||||
required this.company,
|
required this.company,
|
||||||
required this.unit,
|
required this.unit,
|
||||||
});
|
this.productNumber,
|
||||||
|
this.productSpecification});
|
||||||
|
|
||||||
final int? id;
|
final int? id;
|
||||||
|
final String? productNumber;
|
||||||
|
final String? productSpecification;
|
||||||
final DateTime? createdAt;
|
final DateTime? createdAt;
|
||||||
final DateTime? updatedAt;
|
final DateTime? updatedAt;
|
||||||
final String? name;
|
final String? name;
|
||||||
|
@ -41,6 +44,8 @@ class ProductModel {
|
||||||
remark: json["remark"],
|
remark: json["remark"],
|
||||||
isDelete: json["isDelete"],
|
isDelete: json["isDelete"],
|
||||||
companyId: json["companyId"],
|
companyId: json["companyId"],
|
||||||
|
productNumber: json['productNumber'],
|
||||||
|
productSpecification: json['productSpecification'],
|
||||||
unitId: json["unitId"],
|
unitId: json["unitId"],
|
||||||
namePinyin: json["namePinyin"],
|
namePinyin: json["namePinyin"],
|
||||||
files: json["files"] == null
|
files: json["files"] == null
|
||||||
|
@ -63,6 +68,8 @@ class ProductModel {
|
||||||
"isDelete": isDelete,
|
"isDelete": isDelete,
|
||||||
"companyId": companyId,
|
"companyId": companyId,
|
||||||
"unitId": unitId,
|
"unitId": unitId,
|
||||||
|
'productNumber': productNumber,
|
||||||
|
'productSpecification': productSpecification,
|
||||||
"namePinyin": namePinyin,
|
"namePinyin": namePinyin,
|
||||||
"files": files.map((x) => x?.toJson()).toList(),
|
"files": files.map((x) => x?.toJson()).toList(),
|
||||||
"company": company?.toJson(),
|
"company": company?.toJson(),
|
||||||
|
|
|
@ -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/components/task_list.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';
|
||||||
|
|
||||||
class TaskPageView extends StatelessWidget {
|
class InventoryInoutView extends StatelessWidget {
|
||||||
TaskPageView({super.key});
|
InventoryInoutView({super.key});
|
||||||
final controller = Get.put(InventoryInoutController());
|
final controller = Get.put(InventoryInoutController());
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return PageView(
|
return PageView(
|
||||||
physics: const BouncingScrollPhysics(),
|
physics: const BouncingScrollPhysics(),
|
||||||
reverse: true,
|
reverse: true,
|
||||||
|
onPageChanged: (index) {
|
||||||
|
controller.changePage(index);
|
||||||
|
},
|
||||||
controller: controller.pageController,
|
controller: controller.pageController,
|
||||||
children: List.generate(
|
children: List.generate(
|
||||||
controller.daysNum,
|
controller.daysNum,
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
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/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';
|
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
|
||||||
|
|
||||||
class TaskPageBody extends StatelessWidget {
|
class TaskPageBody extends StatelessWidget {
|
||||||
|
@ -38,7 +38,7 @@ class TaskPageBody extends StatelessWidget {
|
||||||
Colors.white.withOpacity(.0),
|
Colors.white.withOpacity(.0),
|
||||||
Colors.white.withOpacity(.0),
|
Colors.white.withOpacity(.0),
|
||||||
])),
|
])),
|
||||||
child: TaskPageView(),
|
child: InventoryInoutView(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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/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';
|
||||||
|
import 'package:sk_base_mobile/util/logger_util.dart';
|
||||||
import 'package:sk_base_mobile/util/modal.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/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;
|
||||||
|
@ -36,6 +37,12 @@ class InventoryInoutController extends GetxController {
|
||||||
// getInoutHistory();
|
// getInoutHistory();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> changePage(int index) async {
|
||||||
|
LoggerUtil()
|
||||||
|
.info(DateUtil.format(DateTime.now().add(Duration(days: -index))));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/// 打开出库还是入库选择框
|
/// 打开出库还是入库选择框
|
||||||
Future<void> showInOrOutPickerDialog() async {
|
Future<void> showInOrOutPickerDialog() async {
|
||||||
showDialog(
|
showDialog(
|
||||||
|
|
|
@ -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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,17 +1,24 @@
|
||||||
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: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/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/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/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_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';
|
||||||
|
@ -21,7 +28,7 @@ class NewInventoryInout extends StatelessWidget {
|
||||||
NewInventoryInout({super.key, this.inOrOut = 'in'});
|
NewInventoryInout({super.key, this.inOrOut = 'in'});
|
||||||
|
|
||||||
final controller = Get.put(NewInventoryInoutController());
|
final controller = Get.put(NewInventoryInoutController());
|
||||||
|
final dictService = Get.find<DictService>();
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
|
@ -88,12 +95,13 @@ class NewInventoryInout extends StatelessWidget {
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: ScreenAdaper.height(formVerticalGap),
|
height: ScreenAdaper.height(formVerticalGap),
|
||||||
),
|
),
|
||||||
Row(
|
buildPositionBottomPicker(),
|
||||||
children: [Expanded(child: buildRemark())],
|
|
||||||
),
|
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: ScreenAdaper.height(formVerticalGap),
|
height: ScreenAdaper.height(formVerticalGap),
|
||||||
),
|
),
|
||||||
|
Row(
|
||||||
|
children: [Expanded(child: buildRemark())],
|
||||||
|
),
|
||||||
buildImageUploadQueue()
|
buildImageUploadQueue()
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -116,16 +124,16 @@ class NewInventoryInout extends StatelessWidget {
|
||||||
|
|
||||||
/// 项目
|
/// 项目
|
||||||
Widget buildProjectPicker() {
|
Widget buildProjectPicker() {
|
||||||
return ZtSearchSelect(
|
return ZtSearchSelect<ProjectModel>(
|
||||||
isRequired: true,
|
isRequired: true,
|
||||||
textController: controller.projectTextController,
|
textController: controller.projectTextController,
|
||||||
labelText: '项目',
|
labelText: '项目',
|
||||||
itemBuilder: (itemData) => Container(
|
itemBuilder: (_, itemData) => Container(
|
||||||
padding: EdgeInsets.symmetric(
|
padding: EdgeInsets.symmetric(
|
||||||
vertical: ScreenAdaper.height(15),
|
vertical: ScreenAdaper.height(15),
|
||||||
horizontal: ScreenAdaper.width(20)),
|
horizontal: ScreenAdaper.width(20)),
|
||||||
child: Text(
|
child: Text(
|
||||||
'${itemData?.name}',
|
'${itemData.name}',
|
||||||
style: TextStyle(fontSize: ScreenAdaper.sp(20)),
|
style: TextStyle(fontSize: ScreenAdaper.sp(20)),
|
||||||
)),
|
)),
|
||||||
suggestionsCallback: (String keyword) {
|
suggestionsCallback: (String keyword) {
|
||||||
|
@ -142,24 +150,168 @@ class NewInventoryInout extends StatelessWidget {
|
||||||
|
|
||||||
/// 产品
|
/// 产品
|
||||||
Widget buildProductPicker() {
|
Widget buildProductPicker() {
|
||||||
return ZtSearchSelect(
|
return TextFormField(
|
||||||
isRequired: true,
|
controller: controller.positionTextController,
|
||||||
textController: controller.productTextController,
|
decoration: const InputDecoration(
|
||||||
labelText: '产品',
|
labelText: '产品',
|
||||||
itemBuilder: (itemData) => ListTile(
|
floatingLabelBehavior: FloatingLabelBehavior.always),
|
||||||
title: Text('${itemData?.name}'),
|
readOnly: true,
|
||||||
subtitle: Text('${itemData?.company?.name ?? ''}'),
|
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,
|
||||||
),
|
),
|
||||||
suggestionsCallback: (String keyword) {
|
topRight: Radius.circular(30)),
|
||||||
return controller.getProducts(keyword: keyword);
|
child: Container(
|
||||||
|
height: Get.height - ScreenAdaper.height(200),
|
||||||
|
width: Get.width - ScreenAdaper.width(200),
|
||||||
|
child: ProductSearch(),
|
||||||
|
),
|
||||||
|
))));
|
||||||
},
|
},
|
||||||
onClear: () {
|
transitionBuilder: (_, anim, __, child) {
|
||||||
controller.payload.remove('productId');
|
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,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
onSelected: (ProductModel product) {
|
);
|
||||||
controller.productTextController.text = product.name ?? '';
|
|
||||||
controller.payload['productId'] = product.id;
|
|
||||||
});
|
});
|
||||||
|
// 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() {
|
Widget buildImageUploadQueue() {
|
||||||
return Container(
|
return Container(
|
||||||
|
@ -217,7 +436,7 @@ class NewInventoryInout extends StatelessWidget {
|
||||||
Text(
|
Text(
|
||||||
'*请拍照上传产品照片',
|
'*请拍照上传产品照片',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: ScreenAdaper.sp(30),
|
fontSize: ScreenAdaper.sp(25),
|
||||||
color: AppTheme.secondPrimaryColor),
|
color: AppTheme.secondPrimaryColor),
|
||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
|
@ -247,8 +466,8 @@ class NewInventoryInout extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
margin: EdgeInsets.symmetric(horizontal: ScreenAdaper.width(5)),
|
margin: EdgeInsets.symmetric(horizontal: ScreenAdaper.width(5)),
|
||||||
width: ScreenAdaper.width(200),
|
width: ScreenAdaper.width(180),
|
||||||
height: ScreenAdaper.width(200),
|
height: ScreenAdaper.width(180),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
border: Border.all(width: 1.0, color: AppTheme.dividerColor),
|
border: Border.all(width: 1.0, color: AppTheme.dividerColor),
|
||||||
|
@ -303,8 +522,8 @@ class NewInventoryInout extends StatelessWidget {
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
margin: EdgeInsets.symmetric(horizontal: ScreenAdaper.width(5)),
|
margin: EdgeInsets.symmetric(horizontal: ScreenAdaper.width(5)),
|
||||||
width: ScreenAdaper.width(200),
|
width: ScreenAdaper.width(180),
|
||||||
height: ScreenAdaper.width(200),
|
height: ScreenAdaper.width(180),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
border: Border.all(width: 1.0, color: AppTheme.dividerColor),
|
border: Border.all(width: 1.0, color: AppTheme.dividerColor),
|
||||||
|
|
|
@ -44,6 +44,7 @@ class NewInventoryInoutController extends GetxController {
|
||||||
final quantityTextController = TextEditingController();
|
final quantityTextController = TextEditingController();
|
||||||
final agentTextController = TextEditingController();
|
final agentTextController = TextEditingController();
|
||||||
final remarkTextController = TextEditingController();
|
final remarkTextController = 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 = {};
|
||||||
|
@ -60,7 +61,7 @@ class NewInventoryInoutController extends GetxController {
|
||||||
|
|
||||||
Future<List<ProductModel>> getProducts({String? keyword}) async {
|
Future<List<ProductModel>> getProducts({String? keyword}) async {
|
||||||
final res =
|
final res =
|
||||||
await Api.getProducts({'page': 1, 'pageSize': 10, 'name': keyword});
|
await Api.getProducts({'page': 1, 'pageSize': 10, 'keyword': keyword});
|
||||||
if (res.data != null) {
|
if (res.data != null) {
|
||||||
products.assignAll(
|
products.assignAll(
|
||||||
res.data!.items.map((e) => ProductModel.fromJson(e)).toList());
|
res.data!.items.map((e) => ProductModel.fromJson(e)).toList());
|
||||||
|
|
|
@ -5,7 +5,6 @@ import 'package:get/get.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/logger_util.dart';
|
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/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/auth.dart';
|
||||||
import 'package:sk_base_mobile/models/user_info.model.dart';
|
import 'package:sk_base_mobile/models/user_info.model.dart';
|
||||||
import 'package:sk_base_mobile/services/service.dart';
|
import 'package:sk_base_mobile/services/service.dart';
|
||||||
|
|
|
@ -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 ??
|
||||||
|
[];
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,8 @@
|
||||||
|
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/widgets/core/zt_bottomsheet_picker.dart';
|
||||||
|
|
||||||
class ModalUtil {
|
class ModalUtil {
|
||||||
static Future<bool> showWarningDialog(
|
static Future<bool> showWarningDialog(
|
||||||
|
@ -40,4 +42,27 @@ class ModalUtil {
|
||||||
);
|
);
|
||||||
return confirmed;
|
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);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}日'));
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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!,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,14 +4,14 @@ 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/util/screen_adaper_util.dart';
|
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
|
||||||
|
|
||||||
class ZtSearchSelect extends StatelessWidget {
|
class ZtSearchSelect<T> extends StatelessWidget {
|
||||||
final TextEditingController textController;
|
final TextEditingController textController;
|
||||||
final Function? suggestionsCallback;
|
final Function? suggestionsCallback;
|
||||||
final Function? onSelected;
|
final void Function(T)? onSelected;
|
||||||
final VoidCallback? onClear;
|
final VoidCallback? onClear;
|
||||||
final String labelText;
|
final String labelText;
|
||||||
final bool isRequired;
|
final bool isRequired;
|
||||||
final Widget Function(dynamic itemData)? itemBuilder;
|
final Widget Function(BuildContext, T) itemBuilder;
|
||||||
const ZtSearchSelect(
|
const ZtSearchSelect(
|
||||||
{super.key,
|
{super.key,
|
||||||
required this.textController,
|
required this.textController,
|
||||||
|
@ -89,13 +89,13 @@ class ZtSearchSelect extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
itemBuilder: (context, itemData) {
|
itemBuilder: (context, itemData) {
|
||||||
return itemBuilder!(itemData);
|
return itemBuilder(context, itemData as T);
|
||||||
},
|
},
|
||||||
onSelected: (selectedItemData) {
|
onSelected: (selectedItemData) {
|
||||||
// 键盘关闭
|
// 键盘关闭
|
||||||
FocusScope.of(context).unfocus();
|
FocusScope.of(context).unfocus();
|
||||||
if (onSelected != null) {
|
if (onSelected != null) {
|
||||||
onSelected!(selectedItemData);
|
onSelected!(selectedItemData as T);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,14 +12,15 @@ class ZtTextInput extends StatelessWidget {
|
||||||
final String labelText;
|
final String labelText;
|
||||||
final String? hint;
|
final String? hint;
|
||||||
final bool isTextArea;
|
final bool isTextArea;
|
||||||
const ZtTextInput(
|
const ZtTextInput({
|
||||||
{super.key,
|
super.key,
|
||||||
required this.textController,
|
required this.textController,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
this.hint,
|
this.hint,
|
||||||
this.isRequired = false,
|
this.isRequired = false,
|
||||||
this.labelText = '',
|
this.labelText = '',
|
||||||
this.isTextArea = false});
|
this.isTextArea = false,
|
||||||
|
});
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return TextFormField(
|
return TextFormField(
|
||||||
|
@ -27,7 +28,7 @@ class ZtTextInput extends StatelessWidget {
|
||||||
onTapOutside: (event) {
|
onTapOutside: (event) {
|
||||||
FocusScope.of(context).unfocus();
|
FocusScope.of(context).unfocus();
|
||||||
},
|
},
|
||||||
maxLines: isTextArea ? 3 : 1, // 添加这行代码
|
maxLines: isTextArea ? 2 : 1, // 添加这行代码
|
||||||
onTap: onTap ?? () {},
|
onTap: onTap ?? () {},
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
floatingLabelBehavior: FloatingLabelBehavior.always,
|
floatingLabelBehavior: FloatingLabelBehavior.always,
|
||||||
|
|
|
@ -10,10 +10,10 @@ class Empty extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Center(
|
return Center(
|
||||||
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
|
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||||
Center(
|
// Center(
|
||||||
child: Image(
|
// child: Image(
|
||||||
height: ScreenAdaper.height(130),
|
// height: ScreenAdaper.height(130),
|
||||||
image: const AssetImage('assets/images/empty_icon.png'))),
|
// image: const AssetImage('assets/images/empty_icon.png'))),
|
||||||
Text(
|
Text(
|
||||||
text ?? '',
|
text ?? '',
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
|
|
|
@ -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),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,9 @@
|
||||||
import 'package:flutter/cupertino.dart';
|
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: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/util/screen_adaper_util.dart';
|
||||||
|
import 'package:sk_base_mobile/widgets/loading_indicator.dart';
|
||||||
|
|
||||||
class RefreshFooter extends StatefulWidget {
|
class RefreshFooter extends StatefulWidget {
|
||||||
const RefreshFooter({Key? key}) : super(key: key);
|
const RefreshFooter({Key? key}) : super(key: key);
|
||||||
|
@ -32,16 +35,16 @@ class _RefreshFooterState extends State<RefreshFooter>
|
||||||
builder: (BuildContext context, LoadStatus? status) {
|
builder: (BuildContext context, LoadStatus? status) {
|
||||||
Widget body;
|
Widget body;
|
||||||
if (status == LoadStatus.idle) {
|
if (status == LoadStatus.idle) {
|
||||||
body = const Text("Pull up to get latest messages");
|
body = const Text("上拉获取更多");
|
||||||
} else if (status == LoadStatus.loading) {
|
} else if (status == LoadStatus.loading) {
|
||||||
body = const SizedBox(child: const CupertinoActivityIndicator());
|
body = const LoadingIndicator();
|
||||||
} else if (status == LoadStatus.failed) {
|
} else if (status == LoadStatus.failed) {
|
||||||
body = Text(
|
body = Text(
|
||||||
"Load failed, please try again",
|
"获取失败,请重试",
|
||||||
style: style,
|
style: style,
|
||||||
);
|
);
|
||||||
} else if (status == LoadStatus.canLoading) {
|
} else if (status == LoadStatus.canLoading) {
|
||||||
body = const CupertinoActivityIndicator(
|
body = const LoadingIndicator(
|
||||||
animating: false,
|
animating: false,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
|
@ -1,6 +1,9 @@
|
||||||
import 'package:flutter/cupertino.dart';
|
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: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/util/screen_adaper_util.dart';
|
||||||
|
import 'package:sk_base_mobile/widgets/loading_indicator.dart';
|
||||||
|
|
||||||
class RefreshHeader extends RefreshIndicator {
|
class RefreshHeader extends RefreshIndicator {
|
||||||
RefreshHeader()
|
RefreshHeader()
|
||||||
|
@ -39,15 +42,17 @@ class RefreshHeaderState extends RefreshIndicatorState<RefreshHeader>
|
||||||
TextStyle style = TextStyle(fontSize: ScreenAdaper.sp(18));
|
TextStyle style = TextStyle(fontSize: ScreenAdaper.sp(18));
|
||||||
Widget body;
|
Widget body;
|
||||||
if (mode == RefreshStatus.refreshing) {
|
if (mode == RefreshStatus.refreshing) {
|
||||||
body = const CupertinoActivityIndicator();
|
body = const LoadingIndicator();
|
||||||
} else if (mode == RefreshStatus.canRefresh) {
|
} else if (mode == RefreshStatus.canRefresh) {
|
||||||
body = const CupertinoActivityIndicator(animating: false);
|
body = const LoadingIndicator(
|
||||||
|
animating: false,
|
||||||
|
);
|
||||||
} else if (mode == RefreshStatus.completed) {
|
} else if (mode == RefreshStatus.completed) {
|
||||||
body = const SizedBox();
|
body = const SizedBox();
|
||||||
} else if (mode == RefreshStatus.failed) {
|
} else if (mode == RefreshStatus.failed) {
|
||||||
body = Text("No more Data)", style: style);
|
body = Text("没有更多数据了", style: style);
|
||||||
} else {
|
} else {
|
||||||
body = const CupertinoActivityIndicator();
|
body = const LoadingIndicator();
|
||||||
}
|
}
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: ScreenAdaper.height(40),
|
height: ScreenAdaper.height(40),
|
||||||
|
|
|
@ -177,6 +177,14 @@ 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:
|
||||||
|
|
|
@ -57,6 +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
|
||||||
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
|
Loading…
Reference in New Issue