diff --git a/lib/apis/api.dart b/lib/apis/api.dart index 7186e99..d2fec8c 100644 --- a/lib/apis/api.dart +++ b/lib/apis/api.dart @@ -21,12 +21,18 @@ Future getUserInfo() { ); } -// 获取项目信息 +// 分页获取项目列表 Future> getProjects(Map params) { return DioService.dio.get(Urls.getProjects, queryParameters: {'page': 1, 'pageSize': 10, ...params}); } +// 分页获取产品列表 +Future> getProducts(Map params) { + return DioService.dio.get(Urls.getProducts, + queryParameters: {'page': 1, 'pageSize': 10, ...params}); +} + Future logout() { return DioService.dio.post( Urls.logout, diff --git a/lib/app_theme.dart b/lib/app_theme.dart index db9058a..0724b50 100644 --- a/lib/app_theme.dart +++ b/lib/app_theme.dart @@ -23,11 +23,50 @@ class AppTheme { static const String fontName = 'NotoSans'; static const Color activeNavigationBarColor = primaryColor; static const Color notActiveNavigationBarColor = Colors.grey; - static const Color dividerColor = Color.fromARGB(255, 197, 197, 197); + static const Color dividerColor = Color.fromARGB(255, 224, 224, 224); } final theme = ThemeData( + primarySwatch: MaterialColor(AppTheme.primaryColor.value, const { + 50: AppTheme.primaryColorLight, + 100: AppTheme.primaryColorLight, + 200: AppTheme.primaryColorLight, + 300: AppTheme.primaryColorLight, + 400: AppTheme.primaryColorLight, + 500: AppTheme.primaryColor, + 600: AppTheme.primaryColorDark, + 700: AppTheme.primaryColorDark, + 800: AppTheme.primaryColorDark, + 900: AppTheme.primaryColorDark, + }), fontFamily: AppTheme.fontName, + datePickerTheme: DatePickerThemeData( + confirmButtonStyle: ButtonStyle( + textStyle: MaterialStateProperty.resolveWith( + (Set states) { + return TextStyle(color: AppTheme.primaryColor); + }, + ), + ), + todayBorder: const BorderSide(color: AppTheme.primaryColor), + todayBackgroundColor: MaterialStateProperty.resolveWith( + (Set states) { + if (states.contains(MaterialState.selected)) { + return AppTheme.primaryColor; + } + return null; + }, + ), + rangeSelectionBackgroundColor: AppTheme.primaryColor, + dayBackgroundColor: MaterialStateProperty.resolveWith( + (Set states) { + if (states.contains(MaterialState.selected)) { + return AppTheme.primaryColor; + } + return null; + }, + ), + ), visualDensity: VisualDensity.adaptivePlatformDensity, primaryColor: AppTheme.primaryColor, primaryColorDark: AppTheme.primaryColorDark, @@ -41,7 +80,7 @@ final theme = ThemeData( unselectedLabelStyle: TextStyle(fontSize: ScreenAdaper.sp(20)), selectedLabelStyle: TextStyle(fontSize: ScreenAdaper.sp(20)), selectedItemColor: AppTheme.primaryColor), - tabBarTheme: TabBarTheme( + tabBarTheme: const TabBarTheme( indicator: BoxDecoration( border: Border(bottom: BorderSide(width: 3, color: AppTheme.primaryColorDark)), @@ -67,7 +106,7 @@ final theme = ThemeData( borderRadius: BorderRadius.circular(ScreenAdaper.sp(15))), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(ScreenAdaper.sp(15)), - borderSide: BorderSide( + borderSide: const BorderSide( color: AppTheme.primaryColorLight, width: 2), // 选中时的边框颜色和宽度 ), ), diff --git a/lib/constants/global_url.dart b/lib/constants/global_url.dart index 741ebfb..70c1ccb 100644 --- a/lib/constants/global_url.dart +++ b/lib/constants/global_url.dart @@ -7,5 +7,6 @@ class Urls { static String saveUserInfo = 'user/saveUserInfo'; static String getUserInfo = 'account/profile'; static String getProjects = 'project'; + static String getProducts = 'product'; static String updateAvatar = 'user/updateAvatar'; } diff --git a/lib/models/company.model.dart b/lib/models/company.model.dart new file mode 100644 index 0000000..ba792f3 --- /dev/null +++ b/lib/models/company.model.dart @@ -0,0 +1,42 @@ +import 'file.model.dart'; + +class CompanyModel { + CompanyModel({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.name, + required this.isDelete, + required this.files, + }); + + final int? id; + final DateTime? createdAt; + final DateTime? updatedAt; + final String? name; + final int? isDelete; + final List files; + + factory CompanyModel.fromJson(Map json) { + return CompanyModel( + id: json["id"], + createdAt: DateTime.tryParse(json["createdAt"] ?? ""), + updatedAt: DateTime.tryParse(json["updatedAt"] ?? ""), + name: json["name"], + isDelete: json["isDelete"], + files: json["files"] == null + ? [] + : List.from( + json["files"]!.map((x) => FileModel.fromJson(x))), + ); + } + + Map toJson() => { + "id": id, + "createdAt": createdAt?.toIso8601String(), + "updatedAt": updatedAt?.toIso8601String(), + "name": name, + "isDelete": isDelete, + "files": files.map((x) => x?.toJson()).toList(), + }; +} diff --git a/lib/models/dict_item.model.dart b/lib/models/dict_item.model.dart new file mode 100644 index 0000000..dabc91b --- /dev/null +++ b/lib/models/dict_item.model.dart @@ -0,0 +1,53 @@ +class DictItemModel { + DictItemModel({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.creator, + required this.updater, + required this.label, + required this.value, + required this.orderNo, + required this.status, + required this.remark, + }); + + final int? id; + final DateTime? createdAt; + final DateTime? updatedAt; + final String? creator; + final String? updater; + final String? label; + final String? value; + final int? orderNo; + final int? status; + final String? remark; + + factory DictItemModel.fromJson(Map json) { + return DictItemModel( + id: json["id"], + createdAt: DateTime.tryParse(json["createdAt"] ?? ""), + updatedAt: DateTime.tryParse(json["updatedAt"] ?? ""), + creator: json["creator"], + updater: json["updater"], + label: json["label"], + value: json["value"], + orderNo: json["orderNo"], + status: json["status"], + remark: json["remark"], + ); + } + + Map toJson() => { + "id": id, + "createdAt": createdAt?.toIso8601String(), + "updatedAt": updatedAt?.toIso8601String(), + "creator": creator, + "updater": updater, + "label": label, + "value": value, + "orderNo": orderNo, + "status": status, + "remark": remark, + }; +} diff --git a/lib/models/file.model.dart b/lib/models/file.model.dart new file mode 100644 index 0000000..099d179 --- /dev/null +++ b/lib/models/file.model.dart @@ -0,0 +1,53 @@ +class FileModel { + FileModel({ + required this.id, + required this.name, + required this.extName, + required this.path, + required this.type, + required this.size, + required this.createdAt, + required this.username, + required this.bussinessRecordId, + required this.bussinessModule, + }); + + final int? id; + final String? name; + final String? extName; + final String? path; + final String? type; + final String? size; + final DateTime? createdAt; + final String? username; + final int? bussinessRecordId; + final String? bussinessModule; + + factory FileModel.fromJson(Map json) { + return FileModel( + id: json["id"], + name: json["name"], + extName: json["extName"], + path: json["path"], + type: json["type"], + size: json["size"], + createdAt: DateTime.tryParse(json["createdAt"] ?? ""), + username: json["username"], + bussinessRecordId: json["bussinessRecordId"], + bussinessModule: json["bussinessModule"], + ); + } + + Map toJson() => { + "id": id, + "name": name, + "extName": extName, + "path": path, + "type": type, + "size": size, + "createdAt": createdAt?.toIso8601String(), + "username": username, + "bussinessRecordId": bussinessRecordId, + "bussinessModule": bussinessModule, + }; +} diff --git a/lib/models/index.dart b/lib/models/index.dart new file mode 100644 index 0000000..19c575e --- /dev/null +++ b/lib/models/index.dart @@ -0,0 +1,6 @@ +library models; + +export './project.model.dart'; +export './product.model.dart'; +export './dict_item.model.dart'; +export './company.model.dart'; diff --git a/lib/models/inventory_inout_create.model.dart b/lib/models/inventory_inout_create.model.dart new file mode 100644 index 0000000..63eb0b5 --- /dev/null +++ b/lib/models/inventory_inout_create.model.dart @@ -0,0 +1,49 @@ +class InventoryInOutCreateModel { + InventoryInOutCreateModel({ + required this.inOrOut, + required this.productId, + required this.projectId, + required this.time, + required this.quantity, + required this.unitPrice, + required this.amount, + required this.agent, + this.remark, + }); + + final int? inOrOut; + final int? productId; + final int? projectId; + final DateTime? time; + final int? quantity; + final int? unitPrice; + final int? amount; + final String? agent; + final String? remark; + + factory InventoryInOutCreateModel.fromJson(Map json) { + return InventoryInOutCreateModel( + inOrOut: json["inOrOut"], + productId: json["productId"], + projectId: json["projectId"], + time: DateTime.tryParse(json["time"] ?? ""), + quantity: json["quantity"], + unitPrice: json["unitPrice"], + amount: json["amount"], + agent: json["agent"], + remark: json["remark"], + ); + } + + Map toJson() => { + "inOrOut": inOrOut, + "productId": productId, + "projectId": projectId, + "time": time?.toIso8601String(), + "quantity": quantity, + "unitPrice": unitPrice, + "amount": amount, + "agent": agent, + "remark": remark, + }; +} diff --git a/lib/models/product.model.dart b/lib/models/product.model.dart index c394672..dbc25e6 100644 --- a/lib/models/product.model.dart +++ b/lib/models/product.model.dart @@ -1,129 +1,71 @@ +import 'package:sk_base_mobile/models/dict_item.model.dart'; +import 'package:sk_base_mobile/models/file.model.dart'; + +import 'company.model.dart'; + class ProductModel { - int? id; - String? createdAt; - String? updatedAt; - String? name; - String? remark; - int? isDelete; - int? companyId; - int? unitId; - String? namePinyin; - List? files; - Company? company; - Unit? 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, + }); - ProductModel( - {this.id, - this.createdAt, - this.updatedAt, - this.name, - this.remark, - this.isDelete, - this.companyId, - this.unitId, - this.namePinyin, - this.files, - this.company, - this.unit}); + final int? id; + final DateTime? createdAt; + final DateTime? updatedAt; + final String? name; + final String? remark; + final int? isDelete; + final int? companyId; + final int? unitId; + final String? namePinyin; + final List files; + final CompanyModel? company; + final DictItemModel? unit; - ProductModel.fromJson(Map json) { - id = json['id']; - createdAt = json['createdAt']; - updatedAt = json['updatedAt']; - name = json['name']; - remark = json['remark']; - isDelete = json['isDelete']; - companyId = json['companyId']; - unitId = json['unitId']; - namePinyin = json['namePinyin']; - if (json['files'] != null) { - files = []; - json['files'].forEach((v) { - files!.add(new Files.fromJson(v)); - }); - } - company = - json['company'] != null ? new Company.fromJson(json['company']) : null; - unit = json['unit'] != null ? new Unit.fromJson(json['unit']) : null; + factory ProductModel.fromJson(Map json) { + return ProductModel( + id: json["id"], + createdAt: DateTime.tryParse(json["createdAt"] ?? ""), + updatedAt: DateTime.tryParse(json["updatedAt"] ?? ""), + name: json["name"], + remark: json["remark"], + isDelete: json["isDelete"], + companyId: json["companyId"], + unitId: json["unitId"], + namePinyin: json["namePinyin"], + files: json["files"] == null + ? [] + : List.from( + json["files"]!.map((x) => FileModel.fromJson(x))), + company: json["company"] == null + ? null + : CompanyModel.fromJson(json["company"]), + unit: json["unit"] == null ? null : DictItemModel.fromJson(json["unit"]), + ); } - Map toJson() { - final Map data = new Map(); - data['id'] = this.id; - data['createdAt'] = this.createdAt; - data['updatedAt'] = this.updatedAt; - data['name'] = this.name; - data['remark'] = this.remark; - data['isDelete'] = this.isDelete; - data['companyId'] = this.companyId; - data['unitId'] = this.unitId; - data['namePinyin'] = this.namePinyin; - if (this.files != null) { - data['files'] = this.files!.map((v) => v.toJson()).toList(); - } - if (this.company != null) { - data['company'] = this.company!.toJson(); - } - if (this.unit != null) { - data['unit'] = this.unit!.toJson(); - } - return data; - } -} - -class Files { - int? id; - String? path; - - Files({this.id, this.path}); - - Files.fromJson(Map json) { - id = json['id']; - path = json['path']; - } - - Map toJson() { - final Map data = new Map(); - data['id'] = this.id; - data['path'] = this.path; - return data; - } -} - -class Company { - int? id; - String? name; - - Company({this.id, this.name}); - - Company.fromJson(Map json) { - id = json['id']; - name = json['name']; - } - - Map toJson() { - final Map data = new Map(); - data['id'] = this.id; - data['name'] = this.name; - return data; - } -} - -class Unit { - int? id; - String? label; - - Unit({this.id, this.label}); - - Unit.fromJson(Map json) { - id = json['id']; - label = json['label']; - } - - Map toJson() { - final Map data = new Map(); - data['id'] = this.id; - data['label'] = this.label; - return data; - } + Map toJson() => { + "id": id, + "createdAt": createdAt?.toIso8601String(), + "updatedAt": updatedAt?.toIso8601String(), + "name": name, + "remark": remark, + "isDelete": isDelete, + "companyId": companyId, + "unitId": unitId, + "namePinyin": namePinyin, + "files": files.map((x) => x?.toJson()).toList(), + "company": company?.toJson(), + "unit": unit?.toJson(), + }; } diff --git a/lib/screens/new_inventory_inout/components/date_time.dart b/lib/screens/new_inventory_inout/components/date_time.dart index 6e295ab..219541a 100644 --- a/lib/screens/new_inventory_inout/components/date_time.dart +++ b/lib/screens/new_inventory_inout/components/date_time.dart @@ -26,7 +26,7 @@ class DateTimeInput extends StatelessWidget { height: defaultPadding / 2, ), InkWell( - onTap: () => controller.showDatePick(context), + onTap: () => controller.showDatePick(), child: Obx(() => DateTimeContainer( text: controller.selectedDate.isEmpty ? 'dd/mm/yyyy' diff --git a/lib/screens/new_inventory_inout/new_inventory_inout.dart b/lib/screens/new_inventory_inout/new_inventory_inout.dart index 35515d0..33778d5 100644 --- a/lib/screens/new_inventory_inout/new_inventory_inout.dart +++ b/lib/screens/new_inventory_inout/new_inventory_inout.dart @@ -3,10 +3,13 @@ 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/models/product.model.dart'; -import 'package:sk_base_mobile/models/project.model.dart'; -import 'package:sk_base_mobile/widgets/core/search_select.dart'; -import 'package:sk_base_mobile/widgets/empty.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/util/date.util.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/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'; @@ -32,30 +35,31 @@ class NewInventoryInout extends StatelessWidget { ), topRight: Radius.circular(ScreenAdaper.sp(30))), child: GestureDetector( - behavior: HitTestBehavior.translucent, - onTap: () { - // 点击空白处时收起键盘 - FocusScope.of(Get.context!).unfocus(); - }, - child: Stack( - children: [ - Positioned.fill( - child: Column( - children: [ - buildTitle(), - Expanded( - child: SingleChildScrollView( - child: buildForm(), - ), - ), - Obx(() => GradientButton( - onPressed: () => {controller.insertTask(context)}, - isLoading: controller.loading.value, - )) - ], - )) - ], - ))), + behavior: HitTestBehavior.translucent, + onTap: () { + // 点击空白处时收起键盘 + // FocusScope.of(Get.context!).unfocus(); + }, + child: Stack( + children: [ + Positioned.fill( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + buildTitle(), + SingleChildScrollView( + child: buildForm(), + ), + Spacer(), + Obx(() => GradientButton( + onPressed: () => {controller.insertTask(context)}, + isLoading: controller.loading.value, + )) + ], + )) + ], + ), + )), ); } @@ -89,6 +93,8 @@ class NewInventoryInout extends StatelessWidget { } Widget buildForm() { + double formVerticalGap = 20.0; + double formHorizontalGap = 20.0; final children = [ Row(children: [ Expanded( @@ -96,13 +102,37 @@ class NewInventoryInout extends StatelessWidget { child: buildProjectPicker(), ), SizedBox( - width: ScreenAdaper.width(10), + width: ScreenAdaper.width(formHorizontalGap), ), Expanded( flex: 3, child: buildProductPicker(), - ) + ), ]), + SizedBox( + height: ScreenAdaper.height(formVerticalGap), + ), + Row( + children: [ + Expanded(flex: 1, child: buildDatePicker()), + SizedBox( + width: ScreenAdaper.width(formHorizontalGap), + ), + Expanded(flex: 1, child: buildQuantity()), + ], + ), + SizedBox( + height: ScreenAdaper.height(formVerticalGap), + ), + Row( + children: [Expanded(child: buildAgent())], + ), + SizedBox( + height: ScreenAdaper.height(formVerticalGap), + ), + Row( + children: [Expanded(child: buildRemark())], + ), ]; final child = Column( @@ -122,147 +152,95 @@ class NewInventoryInout extends StatelessWidget { )); } + /// 项目 Widget buildProjectPicker() { - return SearchSelectComponent( - textController: controller.projectTextController, - labelText: '项目', - itemTextBuilder: (ProjectModel itemData) => - Text('${itemData?.name}'), - ); - // return TypeAheadField( - // hideOnUnfocus: false, - // controller: controller.projectTextController, - // suggestionsCallback: (String keyword) { - // return controller.getProjects(keyword: keyword); - // }, - // builder: (context, _, focusNode) { - // return TextFormField( - // focusNode: focusNode, - // controller: _, - // decoration: InputDecoration( - // focusedBorder: OutlineInputBorder( - // borderSide: const BorderSide( - // color: AppTheme.primaryColorLight, width: 2), - // borderRadius: BorderRadius.circular(ScreenAdaper.sp(15))), - // suffixIcon: _.text.isNotEmpty && focusNode.hasFocus - // ? IconButton( - // icon: const Icon(Icons.clear), - // // 当点击这个按钮时,清除TextFormField的值 - // onPressed: () => _.clear(), - // ) - // : const SizedBox(), - // floatingLabelBehavior: FloatingLabelBehavior.always, - // label: const Row( - // crossAxisAlignment: CrossAxisAlignment.center, - // mainAxisSize: MainAxisSize.min, - // children: [ - // Text( - // "*", - // style: TextStyle(color: Colors.red), - // ), - // Text( - // "项目", - // ), - // ]))); - // }, - // emptyBuilder: (_) => Container( - // alignment: Alignment.center, - // width: ScreenAdaper.width(200), - // height: ScreenAdaper.height(50), - // child: Text( - // '未找到项目', - // style: TextStyle(fontSize: ScreenAdaper.sp(20)), - // ), - // ), - // itemBuilder: (context, project) { - // return ListTile( - // title: Text(project.name ?? ''), - // ); - // }, - // onSelected: (ProjectModel project) { - // controller.projectTextController.text = project.name ?? ''; - // controller.payload['project'] = project.id; - // }); - // return Obx(() => DropdownButtonFormField( - // isExpanded: true, - // isDense: false, - // style: TextStyle( - // fontSize: ScreenAdaper.sp(22), color: AppTheme.nearlyBlack), - // // value: _dropdownValue, - // decoration: InputDecoration( - // contentPadding: EdgeInsets.symmetric( - // vertical: ScreenAdaper.height(10), - // horizontal: ScreenAdaper.width(10)), - // floatingLabelBehavior: FloatingLabelBehavior.always, - // border: OutlineInputBorder( - // borderRadius: BorderRadius.circular(ScreenAdaper.sp(15))), - // label: const Row( - // crossAxisAlignment: CrossAxisAlignment.center, - // mainAxisSize: MainAxisSize.min, - // children: [ - // Text( - // "*", - // style: TextStyle(color: Colors.red), - // ), - // Text("项目"), - // ]), - // ), - // onChanged: (int? newValue) { - // // setState(() { - // // _dropdownValue = newValue; - // // }); - // }, - // // icon: Icon( - // // Icons.arrow_drop_down_outlined, - // // size: ScreenAdaper.sp(40), - // // ), - // items: controller.projects - // .map>((ProjectModel model) { - // return DropdownMenuItem( - // value: model.id, - // child: Text( - // model.name ?? '', - // style: TextStyle( - // fontSize: ScreenAdaper.sp(30), - // overflow: TextOverflow.ellipsis), - // ), - // ); - // }).toList(), - // )); + return ZtSearchSelect( + isRequired: true, + textController: controller.projectTextController, + labelText: '项目', + itemBuilder: (itemData) => Container( + padding: EdgeInsets.symmetric( + vertical: ScreenAdaper.height(15), + horizontal: ScreenAdaper.width(20)), + child: Text( + '${itemData?.name}', + style: TextStyle(fontSize: ScreenAdaper.sp(20)), + )), + suggestionsCallback: (String keyword) { + return controller.getProjects(keyword: keyword); + }, + onClear: () { + controller.payload.remove('projectId'); + }, + onSelected: (ProjectModel project) { + controller.projectTextController.text = project.name ?? ''; + controller.payload['projectId'] = project.id; + }); } + /// 产品 Widget buildProductPicker() { - return TypeAheadField(suggestionsCallback: (search) { - return [ProductModel(name: 'aaa'), ProductModel(name: 'bbb')]; - }, builder: (context, controller, focusNode) { - return TextField( - controller: controller, - focusNode: focusNode, - decoration: InputDecoration( - floatingLabelBehavior: FloatingLabelBehavior.always, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(ScreenAdaper.sp(15))), - label: const Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - "*", - style: TextStyle(color: Colors.red), - ), - Text("产品"), - ]))); - }, itemBuilder: (context, product) { - return ListTile( - title: Text(product.name ?? ''), - subtitle: Text(product.name ?? ''), - ); - }, onSelected: (city) { - // Navigator.of(context).push( - // MaterialPageRoute( - // builder: (context) => CityPage(city: city), - // ), - // ); - }); + 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; + }); + } + + /// 时间 + Widget buildDatePicker() { + return ZtDatePicker( + textController: controller.dateTextController, + onClear: () { + controller.payload.remove('time'); + }, + onDateSelected: (date) { + if (date != null) { + controller.dateTextController.text = DateUtil.format(date); + controller.payload['time'] = controller.dateTextController.text; + } + }, + ); + } + + /// 数量 + Widget buildQuantity() { + return ZtNumberInput( + textController: controller.quantityTextController, + labelText: '数量', + isRequired: true, + ); + } + + /// 经办人 + Widget buildAgent() { + return ZtTextInput( + textController: controller.agentTextController, + labelText: '经办人', + isRequired: true, + ); + } + + /// 备注 + Widget buildRemark() { + return ZtTextInput( + textController: controller.remarkTextController, + labelText: '备注', + isRequired: true, + isTextArea: true, + ); } } diff --git a/lib/screens/new_inventory_inout/new_inventory_inout_controller.dart b/lib/screens/new_inventory_inout/new_inventory_inout_controller.dart index 6e077ee..22bbbd9 100644 --- a/lib/screens/new_inventory_inout/new_inventory_inout_controller.dart +++ b/lib/screens/new_inventory_inout/new_inventory_inout_controller.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:sk_base_mobile/db_helper/dbHelper.dart'; import 'package:sk_base_mobile/models/base_response.dart'; +import 'package:sk_base_mobile/models/product.model.dart'; import 'package:sk_base_mobile/models/project.model.dart'; import 'package:sk_base_mobile/models/task_model.dart'; import 'package:sk_base_mobile/screens/inventory_inout/inventory_inout_controller.dart'; @@ -30,13 +31,14 @@ class NewInventoryInoutController extends GetxController { final description = TextEditingController().obs; final category = TextEditingController().obs; RxList projects = [].obs; + RxList products = [].obs; final projectTextController = TextEditingController(); + final productTextController = TextEditingController(); + final dateTextController = TextEditingController(); + final quantityTextController = TextEditingController(); + final agentTextController = TextEditingController(); + final remarkTextController = TextEditingController(); Map payload = {}; - @override - void onReady() { - super.onReady(); - getProjects(); - } Future> getProjects({String? keyword}) async { final res = @@ -48,6 +50,16 @@ class NewInventoryInoutController extends GetxController { return projects; } + Future> getProducts({String? keyword}) async { + final res = + await Api.getProducts({'page': 1, 'pageSize': 10, 'name': keyword}); + if (res.data != null) { + products.assignAll( + res.data!.items.map((e) => ProductModel.fromJson(e)).toList()); + } + return products; + } + picStartTime(BuildContext context) async { var picker = await showTimePicker(context: context, initialTime: TimeOfDay.now()); @@ -66,11 +78,11 @@ class NewInventoryInoutController extends GetxController { } } - showDatePick(BuildContext context) async { - var picker = await showDatePicker( - context: context, + showDatePick() async { + final picker = await showDatePicker( + context: Get.context!, initialDate: DateTime.now(), - firstDate: DateTime.now(), + firstDate: DateTime(2000, 1, 1), lastDate: DateTime.now().add(const Duration(days: 7))); if (picker != null) { pickedDate = picker; @@ -125,7 +137,7 @@ class NewInventoryInoutController extends GetxController { if (selectedDate.isEmpty) { // selectedDate.value = // '${Utils.addPrefix(DateTime.now().day.toString())}/${Utils.addPrefix(DateTime.now().month.toString())}/${Utils.addPrefix(DateTime.now().year.toString())}'; - showDatePick(context); + showDatePick(); } if (startTime.isEmpty) { picStartTime(context); diff --git a/lib/util/date.util.dart b/lib/util/date.util.dart index cdf782d..969d8be 100644 --- a/lib/util/date.util.dart +++ b/lib/util/date.util.dart @@ -1,6 +1,11 @@ import 'package:date_format/date_format.dart'; class DateUtil { + /// 格式化日期 默认 YYYY-MM-DD + static String format(DateTime date, {List? formats}) { + return formatDate(date, formats ?? ['yyyy', '-', 'mm', '-', 'dd']); + } + /// 获取几月 static String getMonth(DateTime date) { String formattedDate = '${date.month}月'; diff --git a/lib/widgets/core/zk_date_picker.dart b/lib/widgets/core/zk_date_picker.dart new file mode 100644 index 0000000..650bfaa --- /dev/null +++ b/lib/widgets/core/zk_date_picker.dart @@ -0,0 +1,72 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:get/get_core/src/get_main.dart'; +import 'package:sk_base_mobile/util/date.util.dart'; +import 'package:sk_base_mobile/util/screen_adaper_util.dart'; + +class ZtDatePicker extends StatelessWidget { + final TextEditingController textController; + final VoidCallback? onClear; + final Function? onDateSelected; + const ZtDatePicker({ + super.key, + this.onClear, + this.onDateSelected, + required this.textController, + }); + + @override + Widget build(BuildContext context) { + return TextFormField( + controller: textController, + onTapOutside: (event) { + FocusScope.of(context).unfocus(); + }, + decoration: InputDecoration( + floatingLabelBehavior: FloatingLabelBehavior.always, + prefixIcon: Icon( + Icons.date_range_outlined, + size: ScreenAdaper.sp(40), + ), + suffixIcon: textController.text.isNotEmpty + ? IconButton( + icon: const Icon(Icons.clear), + // 当点击这个按钮时,清除TextFormField的值 + onPressed: () { + textController.clear(); + if (onClear != null) { + onClear!(); + } + }, + ) + : const SizedBox(), + hintText: '请选择', + label: const Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "*", + style: TextStyle(color: Colors.red), + ), + Text( + '日期', + ), + ])), + keyboardType: TextInputType.none, + onTap: () async { + final picker = await showDatePicker( + context: Get.overlayContext!, + initialDate: DateTime.now(), + helpText: '选择的日期', + confirmText: '确定', + cancelText: '取消', + firstDate: DateTime(2000, 1, 1), + lastDate: DateTime.now().add(const Duration(days: 7))); + if (onDateSelected != null) { + onDateSelected!(picker); + } + }, + ); + } +} diff --git a/lib/widgets/core/zt_number_input.dart b/lib/widgets/core/zt_number_input.dart new file mode 100644 index 0000000..883c13f --- /dev/null +++ b/lib/widgets/core/zt_number_input.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:sk_base_mobile/app_theme.dart'; +import 'package:sk_base_mobile/constants/bg_color.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'; + +class ZtNumberInput extends StatelessWidget { + final TextEditingController textController; + final VoidCallback? onTap; + final bool isRequired; + final String labelText; + final String? hint; + ZtNumberInput( + {super.key, + required this.textController, + this.onTap, + this.hint, + this.isRequired = false, + this.labelText = ''}); + @override + Widget build(BuildContext context) { + return TextFormField( + controller: textController, + onTapOutside: (event) { + FocusScope.of(context).unfocus(); + }, + onTap: onTap ?? () {}, + keyboardType: TextInputType.number, + decoration: InputDecoration( + floatingLabelBehavior: FloatingLabelBehavior.always, + label: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + if (isRequired) + const Text( + "*", + style: TextStyle(color: Colors.red), + ), + Text( + labelText, + ), + ]), + focusedBorder: OutlineInputBorder( + borderSide: + const BorderSide(color: AppTheme.primaryColorLight, width: 2), + borderRadius: BorderRadius.circular(ScreenAdaper.sp(15))), + hintText: hint ?? '请输入', + ), + ); + } +} diff --git a/lib/widgets/core/search_select.dart b/lib/widgets/core/zt_search_select.dart similarity index 71% rename from lib/widgets/core/search_select.dart rename to lib/widgets/core/zt_search_select.dart index d551af7..bdb0813 100644 --- a/lib/widgets/core/search_select.dart +++ b/lib/widgets/core/zt_search_select.dart @@ -1,22 +1,25 @@ 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/util/screen_adaper_util.dart'; -class SearchSelectComponent extends StatelessWidget { +class ZtSearchSelect extends StatelessWidget { final TextEditingController textController; final Function? suggestionsCallback; final Function? onSelected; + final VoidCallback? onClear; final String labelText; final bool isRequired; - final Widget Function(T itemData)? itemTextBuilder; - const SearchSelectComponent( + final Widget Function(dynamic itemData)? itemBuilder; + const ZtSearchSelect( {super.key, required this.textController, required this.labelText, - required this.itemTextBuilder, - this.suggestionsCallback, + required this.itemBuilder, + required this.suggestionsCallback, this.onSelected, + this.onClear, this.isRequired = false}); @override @@ -29,10 +32,17 @@ class SearchSelectComponent extends StatelessWidget { ? suggestionsCallback!(keyword) : []; }, + debounceDuration: const Duration(milliseconds: 500), + itemSeparatorBuilder: (context, index) => const Divider( + color: AppTheme.dividerColor, + ), builder: (context, _, focusNode) { return TextFormField( focusNode: focusNode, controller: _, + onTapOutside: (event) { + FocusScope.of(context).unfocus(); + }, decoration: InputDecoration( focusedBorder: OutlineInputBorder( borderSide: const BorderSide( @@ -42,10 +52,16 @@ class SearchSelectComponent extends StatelessWidget { ? IconButton( icon: const Icon(Icons.clear), // 当点击这个按钮时,清除TextFormField的值 - onPressed: () => _.clear(), + onPressed: () { + _.clear(); + if (onClear != null) { + onClear!(); + } + }, ) : const SizedBox(), floatingLabelBehavior: FloatingLabelBehavior.always, + hintText: '请选择', label: Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.min, @@ -70,12 +86,14 @@ class SearchSelectComponent extends StatelessWidget { ), ), itemBuilder: (context, itemData) { - return ListTile( - title: itemTextBuilder!(itemData), - ); + return itemBuilder!(itemData); }, onSelected: (selectedItemData) { - onSelected ?? onSelected!(selectedItemData); + // 键盘关闭 + FocusScope.of(context).unfocus(); + if (onSelected != null) { + onSelected!(selectedItemData); + } }); } } diff --git a/lib/widgets/core/zt_text_input.dart b/lib/widgets/core/zt_text_input.dart new file mode 100644 index 0000000..34d6c6d --- /dev/null +++ b/lib/widgets/core/zt_text_input.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:sk_base_mobile/app_theme.dart'; +import 'package:sk_base_mobile/constants/bg_color.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'; + +class ZtTextInput extends StatelessWidget { + final TextEditingController textController; + final VoidCallback? onTap; + final bool isRequired; + 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}); + @override + Widget build(BuildContext context) { + return TextFormField( + controller: textController, + onTapOutside: (event) { + FocusScope.of(context).unfocus(); + }, + maxLines: isTextArea ? null : 1, // 添加这行代码 + onTap: onTap ?? () {}, + decoration: InputDecoration( + floatingLabelBehavior: FloatingLabelBehavior.always, + label: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + if (isRequired) + const Text( + "*", + style: TextStyle(color: Colors.red), + ), + Text( + labelText, + ), + ]), + focusedBorder: OutlineInputBorder( + borderSide: + const BorderSide(color: AppTheme.primaryColorLight, width: 2), + borderRadius: BorderRadius.circular(ScreenAdaper.sp(15))), + hintText: hint ?? '请输入', + ), + ); + } +} diff --git a/lib/widgets/empty.dart b/lib/widgets/empty.dart index ade11bd..f9f6a54 100644 --- a/lib/widgets/empty.dart +++ b/lib/widgets/empty.dart @@ -3,8 +3,8 @@ import 'package:flutter/material.dart'; import 'package:sk_base_mobile/util/screen_adaper_util.dart'; class Empty extends StatelessWidget { - String? text; - Empty({super.key, this.text}); + final String? text; + const Empty({super.key, this.text}); @override Widget build(BuildContext context) { @@ -13,7 +13,7 @@ class Empty extends StatelessWidget { Center( child: Image( height: ScreenAdaper.height(130), - image: AssetImage('assets/images/empty_icon.png'))), + image: const AssetImage('assets/images/empty_icon.png'))), Text( text ?? '', textAlign: TextAlign.center,